feat: 修复Container界面和File App 和 User App
User App 添加permission
This commit is contained in:
49
src/App.tsx
49
src/App.tsx
@@ -31,10 +31,10 @@ const AntProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
colorPrimaryActive: primaryColor,
|
||||
borderRadius: 4,
|
||||
colorBorder: primaryColor,
|
||||
// colorText: primaryColor,
|
||||
colorIcon: primaryColor,
|
||||
colorIconHover: secondaryColor,
|
||||
colorInfoHover: secondaryColor,
|
||||
zIndexPopupBase: 2000,
|
||||
},
|
||||
components: {
|
||||
DatePicker: {
|
||||
@@ -42,6 +42,9 @@ const AntProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
colorPrimaryHover: secondaryColor,
|
||||
colorPrimaryActive: primaryColor,
|
||||
},
|
||||
Tooltip: {
|
||||
zIndexPopupBase: 2000,
|
||||
}
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
@@ -50,29 +53,27 @@ const AntProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
};
|
||||
export const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<CustomThemeProvider >
|
||||
<AntProvider>
|
||||
<div className='w-full h-full'>
|
||||
<Router basename={basename}>
|
||||
<Routes>
|
||||
<Route path='/' element={<Redirect to='/container/' />} />
|
||||
<Route path='/container/*' element={<ContainerApp />} />
|
||||
<Route path='/map/*' element={<MapApp />} />
|
||||
<Route path='/user/*' element={<UserApp />} />
|
||||
<Route path='/org/*' element={<OrgApp />} />
|
||||
<Route path='/app/*' element={<UserAppApp />} />
|
||||
<Route path='/file/*' element={<FileApp />} />
|
||||
<CustomThemeProvider>
|
||||
<AntProvider>
|
||||
<div className='w-full h-full'>
|
||||
<Router basename={basename}>
|
||||
<Routes>
|
||||
<Route path='/' element={<Redirect to='/container/' />} />
|
||||
<Route path='/container/*' element={<ContainerApp />} />
|
||||
<Route path='/map/*' element={<MapApp />} />
|
||||
<Route path='/user/*' element={<UserApp />} />
|
||||
<Route path='/org/*' element={<OrgApp />} />
|
||||
<Route path='/app/*' element={<UserAppApp />} />
|
||||
<Route path='/file/*' element={<FileApp />} />
|
||||
|
||||
<Route path='/404' element={<div>404</div>} />
|
||||
<Route path='*' element={<div>404</div>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
</AntProvider>
|
||||
<div id='for-modal'></div>
|
||||
<ToastContainer />
|
||||
</CustomThemeProvider>
|
||||
</div>
|
||||
<Route path='/404' element={<div>404</div>} />
|
||||
<Route path='*' element={<div>404</div>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
</AntProvider>
|
||||
<div id='for-modal'></div>
|
||||
<ToastContainer />
|
||||
</CustomThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
1
src/components/card/index.tsx
Normal file
1
src/components/card/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './CardBlank';
|
||||
@@ -1,11 +1,8 @@
|
||||
@import 'tailwindcss';
|
||||
@import './assets/styles.css';
|
||||
@import './index.css';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
@theme {
|
||||
--color-primary: white;
|
||||
--color-secondary: #14171a;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
@@ -51,7 +48,7 @@ h3 {
|
||||
}
|
||||
|
||||
@utility layout-menu {
|
||||
@apply bg-gray-900 p-2 text-white flex justify-between h-12;
|
||||
@apply bg-secondary p-2 text-white flex justify-between h-12 ;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
@utility no-drag {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useLayoutStore } from './store';
|
||||
import clsx from 'clsx';
|
||||
import { Button } from 'antd';
|
||||
import { Button } from '@mui/material';
|
||||
import { message } from '@/modules/message';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
SmileOutlined,
|
||||
SwitcherOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { X } from 'lucide-react';
|
||||
import { useNewNavigate } from '../navicate';
|
||||
const meun = [
|
||||
{
|
||||
@@ -58,11 +59,13 @@ export const LayoutMenu = () => {
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}></div>
|
||||
<div className='w-[300px] h-full absolute top-0 left-0 bg-white'>
|
||||
<div className='w-[300px] h-full absolute top-0 left-0 bg-amber-900 text-primary'>
|
||||
<div className='flex justify-between p-6 mt-4 font-bold items-center'>
|
||||
Envision Center
|
||||
<div>
|
||||
<Button icon={<CloseOutlined />} onClick={() => setOpen(false)}></Button>
|
||||
<Button onClick={() => setOpen(false)}>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-3 font-medium'>
|
||||
@@ -70,7 +73,7 @@ export const LayoutMenu = () => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='flex items-center p-4 gap-3 cursor-pointer hover:bg-slate-200 rounded-md'
|
||||
className='flex items-center p-4 gap-3 cursor-pointer hover:bg-secondary hover:text-white rounded-md'
|
||||
onClick={() => {
|
||||
if (item.link) navigate(`${item.link}`);
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MenuOutlined, SwapOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { LayoutMenu } from './Menu';
|
||||
import { useLayoutStore, usePlatformStore } from './store';
|
||||
@@ -9,7 +9,7 @@ import { LayoutUser } from './LayoutUser';
|
||||
import PandaPNG from '@/assets/panda.png';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { IconButton as Button } from '@mui/material';
|
||||
type LayoutMainProps = {
|
||||
title?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
@@ -50,7 +50,7 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
||||
<div className='flex w-full h-full flex-col relative'>
|
||||
<LayoutMenu />
|
||||
<div
|
||||
className={clsx('layout-menu items-center', !mount && '!invisible')}
|
||||
className={clsx('layout-menu items-center ', !mount && '!invisible')}
|
||||
style={{
|
||||
cursor: isElectron ? 'move' : 'default',
|
||||
}}>
|
||||
@@ -58,19 +58,21 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
||||
className={clsx('mr-4 cursor-pointer no-drag', isMac && 'ml-16')}
|
||||
onClick={() => {
|
||||
menuStore.setOpen(true);
|
||||
}}
|
||||
icon={<MenuOutlined />}></Button>
|
||||
<div className='flex grow justify-between'>
|
||||
}}>
|
||||
<MenuOutlined />
|
||||
</Button>
|
||||
<div className='flex grow justify-between pl-4 items-center'>
|
||||
{props.title}
|
||||
<div className='mr-4 flex gap-4 items-center no-drag'>
|
||||
{menuStore.me?.type === 'org' && (
|
||||
<div>
|
||||
<Tooltip title='Switch To User'>
|
||||
<Button
|
||||
icon={<SwapOutlined />}
|
||||
onClick={() => {
|
||||
menuStore.switchOrg('', 'user');
|
||||
}}></Button>
|
||||
}}>
|
||||
<SwapOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -189,11 +189,7 @@ export const AppVersionList = () => {
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => {
|
||||
if (isRunning) {
|
||||
let baseUri = 'https://kevisual.xiongxiao.me';
|
||||
// if (DEV_SERVER) {
|
||||
// baseUri = 'http://localhost:3005';
|
||||
// }
|
||||
const link = new URL(`/test/${item.id}`, baseUri);
|
||||
const link = new URL(`/test/${item.id}`, location.origin);
|
||||
window.open(link.toString(), '_blank');
|
||||
} else {
|
||||
message.error('The app is not running');
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useUserAppStore } from '../store';
|
||||
import { useEffect } from 'react';
|
||||
import { Button, Form, Input, Space, Modal, Select, Tooltip, Switch } from 'antd';
|
||||
import { CodeOutlined, DeleteOutlined, EditOutlined, LinkOutlined, PlusOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Form, Input, Modal, Select, Switch } from 'antd';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import UnorderedListOutlined from '@ant-design/icons/UnorderedListOutlined';
|
||||
import CodeOutlined from '@ant-design/icons/CodeOutlined';
|
||||
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
|
||||
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { DialogActions, Tooltip } from '@mui/material';
|
||||
import { marked } from 'marked';
|
||||
import clsx from 'clsx';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { iText } from '@kevisual/resources/index.ts';
|
||||
import { PermissionManager } from '@kevisual/resources/pages/file/modules/PermissionManager.tsx';
|
||||
import { Button } from '@mui/material';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog, DialogContent, DialogTitle, ButtonGroup } from '@mui/material';
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const containerStore = useUserAppStore(
|
||||
@@ -40,62 +53,130 @@ const FormModal = () => {
|
||||
};
|
||||
const isEdit = containerStore.formData.id;
|
||||
return (
|
||||
<Modal
|
||||
<Dialog
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={containerStore.showEdit}
|
||||
onClose={() => containerStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='title' label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='domain' label='domain'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='key' label='key'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name='proxy' label='proxy' tooltip='静态网站设置,如果是静态网站,不需要重定向到index.html的页面。'>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name='status' label='status'>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'Running', value: 'running' },
|
||||
{ label: 'Stop', value: 'stop' },
|
||||
]}
|
||||
/>
|
||||
</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>
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
width: '800px',
|
||||
},
|
||||
}}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
initialValues={{
|
||||
proxy: true,
|
||||
}}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='title' label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='domain' label='domain' tooltip='域名自定义绑定'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='key' label='key'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name='proxy' label='proxy' tooltip='设置为true,则后端直接代理请求minio服务进行转发,不会缓存下载到服务器。'>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name='status' label='status'>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'Running', value: 'running' },
|
||||
{ label: 'Stop', value: 'stop' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='submit'>提交</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
const ShareModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [permission, setPermission] = useState<any>(null);
|
||||
const containerStore = useUserAppStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
showEdit: state.showShareEdit,
|
||||
setShowEdit: state.setShowShareEdit,
|
||||
formData: state.formData,
|
||||
updateData: state.updateData,
|
||||
};
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
const open = containerStore.showEdit;
|
||||
if (open) {
|
||||
// form.setFieldsValue(containerStore.formData);
|
||||
const permission = containerStore.formData?.data?.permission || {};
|
||||
if (isObjectNull(permission)) {
|
||||
setPermission(null);
|
||||
} else {
|
||||
setPermission(permission);
|
||||
}
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
const onFinish = async () => {
|
||||
const values = {
|
||||
...containerStore.formData,
|
||||
data: {
|
||||
permission,
|
||||
},
|
||||
};
|
||||
containerStore.updateData(values);
|
||||
};
|
||||
const onClose = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
};
|
||||
return (
|
||||
<Dialog
|
||||
open={containerStore.showEdit}
|
||||
onClose={() => {
|
||||
containerStore.setShowEdit(false);
|
||||
}}>
|
||||
<DialogTitle>{iText.share.title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<PermissionManager
|
||||
value={permission}
|
||||
onChange={(value) => {
|
||||
setPermission(value);
|
||||
}}
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button type='submit' variant='contained' onClick={onFinish}>
|
||||
提交
|
||||
</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const List = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
@@ -108,6 +189,7 @@ export const List = () => {
|
||||
formData: state.formData,
|
||||
setFormData: state.setFormData,
|
||||
deleteData: state.deleteData,
|
||||
setShowShareEdit: state.setShowShareEdit,
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -116,19 +198,29 @@ export const List = () => {
|
||||
userAppStore.getList();
|
||||
}, []);
|
||||
return (
|
||||
<div className='w-full h-full flex bg-slate-100'>
|
||||
<div className='w-full h-full flex bg-slate-100 text-primary'>
|
||||
<div className='p-2 h-full bg-white flex flex-col gap-2'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
userAppStore.setShowEdit(true);
|
||||
}}
|
||||
icon={<PlusOutlined />}></Button>
|
||||
<Tooltip title='To Container'>
|
||||
<Button
|
||||
<Tooltip title='添加一个应用'>
|
||||
<IconButton
|
||||
sx={{
|
||||
padding: '8px',
|
||||
}}
|
||||
onClick={() => {
|
||||
userAppStore.setShowEdit(true);
|
||||
}}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title='转到Container管理界面'>
|
||||
<IconButton
|
||||
sx={{
|
||||
padding: '8px',
|
||||
}}
|
||||
onClick={() => {
|
||||
navicate('/container');
|
||||
}}
|
||||
icon={<CodeOutlined />}></Button>
|
||||
}}>
|
||||
<CodeOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='grow'>
|
||||
@@ -140,40 +232,55 @@ export const List = () => {
|
||||
const hasDescription = !!item.description;
|
||||
const content = marked.parse(item.description);
|
||||
return (
|
||||
<div className='card border-t border-gray-200 w-[300px] ' key={item.id}>
|
||||
<div className='card-title flex justify-between' onClick={() => {}}>
|
||||
<div className='card w-[300px] ' key={item.id}>
|
||||
<div className='card-title flex font-bold justify-between' onClick={() => {}}>
|
||||
{item.title}
|
||||
<div>
|
||||
<div className={`${isRunning ? 'bg-green-500' : 'bg-red-500'} w-4 h-4 rounded-full`}></div>
|
||||
<Tooltip title={isRunning ? '网页可正常访问' : '网页被关闭'}>
|
||||
<div className={`${isRunning ? 'bg-green-500' : 'bg-red-500'} w-4 h-4 rounded-full`}></div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='text-xs'>domain: {item.domain}</div>
|
||||
{item.domain && <div className='text-xs'>访问域名: {item.domain}</div>}
|
||||
<div className='text-xs'>version: {item.version}</div>
|
||||
<div className={clsx('text-sm border border-gray-200 p-2 max-h-[140px] scrollbar my-1', !hasDescription && 'hidden')}>
|
||||
<div dangerouslySetInnerHTML={{ __html: content }}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2'>
|
||||
<Space.Compact>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title={'Edit'}>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
userAppStore.setFormData(item);
|
||||
userAppStore.setShowEdit(true);
|
||||
}}></Button>
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={'App Version List'}>
|
||||
<Button
|
||||
icon={<UnorderedListOutlined />}
|
||||
onClick={() => {
|
||||
navicate(`/app/${item.key}/version/list`);
|
||||
}}></Button>
|
||||
}}>
|
||||
<UnorderedListOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={iText.share.tips}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
userAppStore.setFormData(item);
|
||||
userAppStore.setShowShareEdit(true);
|
||||
}}>
|
||||
<ShareAltOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={'To App'}>
|
||||
<Button
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => {
|
||||
if (isRunning) {
|
||||
let baseUri = location.origin;
|
||||
@@ -192,11 +299,12 @@ export const List = () => {
|
||||
} else {
|
||||
message.error('The app is not running');
|
||||
}
|
||||
}}></Button>
|
||||
}}>
|
||||
<LinkOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Delete'}>
|
||||
<Button
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
console.log('delete', item);
|
||||
modal.confirm({
|
||||
@@ -207,9 +315,11 @@ export const List = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}></Button>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -220,6 +330,7 @@ export const List = () => {
|
||||
</div>
|
||||
{contextHolder}
|
||||
<FormModal />
|
||||
<ShareModal />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,8 @@ type UserAppStore = {
|
||||
getList: () => Promise<void>;
|
||||
updateData: (data: any) => Promise<void>;
|
||||
deleteData: (id: string) => Promise<void>;
|
||||
showShareEdit: boolean;
|
||||
setShowShareEdit: (showShareEdit: boolean) => void;
|
||||
};
|
||||
export const useUserAppStore = create<UserAppStore>((set, get) => {
|
||||
return {
|
||||
@@ -45,7 +47,7 @@ export const useUserAppStore = create<UserAppStore>((set, get) => {
|
||||
});
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
set({ showEdit: false, formData: res.data });
|
||||
set({ showEdit: false, showShareEdit: false, formData: res.data });
|
||||
getList();
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
@@ -65,5 +67,7 @@ export const useUserAppStore = create<UserAppStore>((set, get) => {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
showShareEdit: false,
|
||||
setShowShareEdit: (showShareEdit) => set({ showShareEdit }),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Input, Modal, Select, Space, Switch, Table, Tooltip } from 'antd';
|
||||
import { Input, Modal, Select, Space, Switch } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { TextArea } from '../components/TextArea';
|
||||
import { useContainerStore } from '../store';
|
||||
@@ -7,6 +7,9 @@ import { Form } from 'antd';
|
||||
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,
|
||||
@@ -54,48 +57,44 @@ const FormModal = () => {
|
||||
};
|
||||
const isEdit = containerStore.formData.id;
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={containerStore.showEdit}
|
||||
onClose={() => containerStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='title' label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name='tags' label='tags'>
|
||||
<Select mode='tags' />
|
||||
</Form.Item>
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</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={containerStore.showEdit} onClose={() => containerStore.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='title' label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name='tags' label='tags'>
|
||||
<Select mode='tags' />
|
||||
</Form.Item>
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button variant='contained' type='submit'>
|
||||
提交
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
const PublishFormModal = () => {
|
||||
@@ -106,7 +105,6 @@ const PublishFormModal = () => {
|
||||
showEdit: state.showPublish,
|
||||
setShowEdit: state.setShowPublish,
|
||||
formData: state.formData,
|
||||
publishData: state.publishData,
|
||||
updateData: state.updateData,
|
||||
};
|
||||
}),
|
||||
@@ -122,8 +120,41 @@ const PublishFormModal = () => {
|
||||
}
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
const onFinish = async (values: any) => {
|
||||
containerStore.publishData(values);
|
||||
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 fileName = values['publish']?.['fileName'];
|
||||
let directoryAndName: ReturnType<typeof getDirectoryAndName> | null = null;
|
||||
try {
|
||||
directoryAndName = getDirectoryAndName(fileName);
|
||||
if (!directoryAndName) {
|
||||
message.error('Invalid filename');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('Invalid filename');
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
directory: directoryAndName.directory,
|
||||
});
|
||||
// @ts-ignore
|
||||
if (res.code === 200) {
|
||||
message.success('upload success');
|
||||
} else {
|
||||
message.error('upload failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
const onUpdate = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
@@ -133,57 +164,53 @@ const PublishFormModal = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
};
|
||||
const isEdit = containerStore.formData.id;
|
||||
return (
|
||||
<Modal
|
||||
title={'Publish'}
|
||||
open={containerStore.showEdit}
|
||||
onClose={() => containerStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'title']} label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'description']} label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'version']} label='version' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'key']} label='key' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'fileName']} label='file name' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'saveHTML']} label='save html'>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<div className='flex gap-3'>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Save And Publish
|
||||
</Button>
|
||||
<Button onClick={onUpdate}> Save</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Dialog open={containerStore.showEdit} onClose={() => containerStore.setShowEdit(false)}>
|
||||
<DialogTitle>Publish</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={['publish', 'key']} label='App key' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'version']} label='App Version' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'fileName']} label='Filename' tooltip='可以是文件夹格式,比如(directory/a.name)' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'description']} label='Description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<div className='flex gap-3'>
|
||||
<Tooltip
|
||||
placement='top'
|
||||
title='根据文件名和code的字符串的内容,自动生成文件。并保存。如果是其他文件类型,转成base64上传。比如图片以类似data:image/jpeg;开头'>
|
||||
<Button variant='contained' color='primary' type='submit'>
|
||||
上传
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Button variant='contained' onClick={onUpdate}>
|
||||
保存
|
||||
</Button>
|
||||
<Button onClick={onClose}>取消</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const ContainerList = () => {
|
||||
@@ -216,11 +243,14 @@ export const ContainerList = () => {
|
||||
containerStore.setFormData({});
|
||||
containerStore.setShowEdit(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-full h-full flex '>
|
||||
<div className='p-2 flex flex-col gap-2'>
|
||||
<Tooltip title='add'>
|
||||
<Button onClick={onAdd} icon={<PlusOutlined />}></Button>
|
||||
<Tooltip title='添加'>
|
||||
<IconButton variant='contained' onClick={onAdd} sx={{ padding: '8px' }}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
@@ -255,41 +285,48 @@ export const ContainerList = () => {
|
||||
<TextArea className='max-h-[240px] scrollbar' value={item.code} readonly />
|
||||
</div>
|
||||
<div className='flex mt-2 '>
|
||||
<Space.Compact>
|
||||
<ButtonGroup variant='contained' color='primary'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// containerStore.publishData(item);
|
||||
}}
|
||||
icon={<SettingOutlined />}></Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
containerStore.setFormData(item);
|
||||
containerStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<EditOutlined />}></Button>
|
||||
<Tooltip title='预览'>
|
||||
}}>
|
||||
<SettingOutlined />
|
||||
</Button>
|
||||
<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();
|
||||
}}
|
||||
icon={<LinkOutlined />}></Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='publish'>
|
||||
}}>
|
||||
<LinkOutlined />
|
||||
</Button>
|
||||
</Tooltip> */}
|
||||
<Tooltip title='发布到 user app当中'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
// containerStore.publishData(item);
|
||||
containerStore.setFormData(item);
|
||||
containerStore.setShowPublish(true);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<CloudUploadOutlined />}></Button>
|
||||
}}>
|
||||
<CloudUploadOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='删除'>
|
||||
<Button
|
||||
// color='error'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
modal.confirm({
|
||||
@@ -300,10 +337,11 @@ export const ContainerList = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
@@ -325,24 +363,17 @@ export const ContainerList = () => {
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
containerStore.setFormData({});
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='保存'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log('save', containerStore.formData);
|
||||
containerStore.updateData({ ...containerStore.formData, code });
|
||||
}}
|
||||
icon={<SaveOutlined />}></Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='预览'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
// navicate('/container/preview/' + item.id);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<LinkOutlined />}></Button>
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,9 +12,8 @@ type ContainerStore = {
|
||||
setLoading: (loading: boolean) => void;
|
||||
list: any[];
|
||||
getList: () => Promise<void>;
|
||||
updateData: (data: any) => Promise<void>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean }) => Promise<Boolean>;
|
||||
deleteData: (id: string) => Promise<void>;
|
||||
publishData: (data: any) => Promise<void>;
|
||||
};
|
||||
export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
return {
|
||||
@@ -40,8 +39,10 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
updateData: async (data) => {
|
||||
updateData: async (data, opts) => {
|
||||
const { getList } = get();
|
||||
const closePublish = opts?.closePublish ?? true;
|
||||
const closeEdit = opts?.closeEdit ?? true;
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'update',
|
||||
@@ -49,11 +50,18 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
});
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
set({ showEdit: false, showPublish: false, formData: res.data });
|
||||
set({ formData: res.data });
|
||||
getList();
|
||||
if (closePublish) {
|
||||
set({ showPublish: false });
|
||||
}
|
||||
if (closeEdit) {
|
||||
set({ showEdit: false });
|
||||
}
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
return res.code === 200;
|
||||
},
|
||||
deleteData: async (id) => {
|
||||
const { getList } = get();
|
||||
@@ -69,17 +77,5 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
publishData: async (data) => {
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'publish',
|
||||
data,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -146,12 +146,12 @@ export const List = () => {
|
||||
<div className='flex gap-2 border border-gray-200 shadow-md p-2'>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Tooltip title='所有的资源文件' placement='right'>
|
||||
<IconButton sx={{ py: 1 }} color={tab !== 'folder' ? 'primary' : 'secondary'} onClick={() => setTab('folder')}>
|
||||
<IconButton color={tab !== 'folder' ? 'primary' : 'secondary'} onClick={() => setTab('folder')}>
|
||||
<FolderOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title='上传资源文件管理' placement='right'>
|
||||
<IconButton sx={{ py: 1 }} color={tab !== 'upload' ? 'primary' : 'secondary'} onClick={() => setTab('upload')}>
|
||||
<IconButton color={tab !== 'upload' ? 'primary' : 'secondary'} onClick={() => setTab('upload')}>
|
||||
<UploadOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { Button, Input, Modal, Space, Table } from 'antd';
|
||||
import { Button, Input, Modal, Space } from 'antd';
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { useUserStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { EditOutlined, SettingOutlined, LinkOutlined, SaveOutlined, DeleteOutlined, LeftOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
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';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { CardBlank } from '@kevisual/center-components/card/CardBlank.tsx';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog } from '@mui/material';
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const userStore = useUserStore(
|
||||
@@ -41,14 +48,15 @@ const FormModal = () => {
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
return (
|
||||
<Modal
|
||||
<Dialog
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
width: '800px',
|
||||
},
|
||||
}}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
@@ -76,11 +84,10 @@ const FormModal = () => {
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const List = () => {
|
||||
const navicate = useNewNavigate();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const userStore = useUserStore(
|
||||
useShallow((state) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ 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';
|
||||
import UploadOutlined from '@ant-design/icons/UploadOutlined';
|
||||
import PandaPNG from '@/assets/panda.png';
|
||||
import { FileUpload } from '../module/FileUpload';
|
||||
export const Profile = () => {
|
||||
|
||||
Reference in New Issue
Block a user