feat: 暂存,添加上传文件
This commit is contained in:
		
							
								
								
									
										153
									
								
								src/pages/app/edit/AppVersionList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/pages/app/edit/AppVersionList.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| import { useParams } from 'react-router'; | ||||
| import { useAppVersionStore } from '../store'; | ||||
| import { useShallow } from 'zustand/react/shallow'; | ||||
| import { useEffect } from 'react'; | ||||
| import { Button, Form, Input, Modal } from 'antd'; | ||||
| import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'; | ||||
| import { isObjectNull } from '@/utils/is-null'; | ||||
| import { useNavigate } from 'react-router'; | ||||
| import { FileUpload } from '../modules/FileUpload'; | ||||
| const FormModal = () => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const containerStore = useAppVersionStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         showEdit: state.showEdit, | ||||
|         setShowEdit: state.setShowEdit, | ||||
|         formData: state.formData, | ||||
|         updateData: state.updateData, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   useEffect(() => { | ||||
|     const open = containerStore.showEdit; | ||||
|     if (open) { | ||||
|       if (open) { | ||||
|         const isNull = isObjectNull(containerStore.formData); | ||||
|         if (isNull) { | ||||
|           form.setFieldsValue({}); | ||||
|         } else form.setFieldsValue(containerStore.formData); | ||||
|       } | ||||
|     } | ||||
|   }, [containerStore.showEdit]); | ||||
|   const onFinish = async (values: any) => { | ||||
|     containerStore.updateData(values); | ||||
|   }; | ||||
|   const onClose = () => { | ||||
|     containerStore.setShowEdit(false); | ||||
|     form.resetFields(); | ||||
|   }; | ||||
|   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='key' label='key'> | ||||
|           <Input disabled /> | ||||
|         </Form.Item> | ||||
|         <Form.Item name='version' label='version'> | ||||
|           <Input /> | ||||
|         </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> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const AppVersionList = () => { | ||||
|   const params = useParams(); | ||||
|   const appKey = params.appKey; | ||||
|   const versionStore = useAppVersionStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         list: state.list, | ||||
|         getList: state.getList, | ||||
|         setKey: state.setKey, | ||||
|         setShowEdit: state.setShowEdit, | ||||
|         setFormData: state.setFormData, | ||||
|         deleteData: state.deleteData, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   useEffect(() => { | ||||
|     // fetch app version list | ||||
|     if (appKey) { | ||||
|       versionStore.setKey(appKey); | ||||
|       versionStore.getList(); | ||||
|     } | ||||
|   }, []); | ||||
|   return ( | ||||
|     <div className='w-full h-full flex bg-slate-100'> | ||||
|       <div className='p-2 bg-white'> | ||||
|         <Button | ||||
|           onClick={() => { | ||||
|             versionStore.setFormData({ key: appKey }); | ||||
|             versionStore.setShowEdit(true); | ||||
|           }} | ||||
|           icon={<PlusOutlined />} | ||||
|         /> | ||||
|       </div> | ||||
|       <div> | ||||
|         <FileUpload /> | ||||
|       </div> | ||||
|       <div className='flex-grow h-full'> | ||||
|         <div className='w-full h-full p-4'> | ||||
|           <div className='w-full h-full rounded-lg bg-white'> | ||||
|             <div className='flex gap-2 flex-wrap p-4'> | ||||
|               {versionStore.list.map((item, index) => { | ||||
|                 return ( | ||||
|                   <div className='card border-t' key={index}> | ||||
|                     <div>{item.version}</div> | ||||
|  | ||||
|                     <div> | ||||
|                       <Button.Group> | ||||
|                         <Button | ||||
|                           onClick={() => { | ||||
|                             versionStore.setFormData(item); | ||||
|                             versionStore.setShowEdit(true); | ||||
|                           }} | ||||
|                           icon={<EditOutlined />} | ||||
|                         /> | ||||
|                         <Button | ||||
|                           onClick={() => { | ||||
|                             versionStore.deleteData(item.id); | ||||
|                           }} | ||||
|                           icon={<DeleteOutlined />} | ||||
|                         /> | ||||
|                       </Button.Group> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 ); | ||||
|               })} | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <FormModal /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										129
									
								
								src/pages/app/edit/List.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/pages/app/edit/List.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| import { useShallow } from 'zustand/react/shallow'; | ||||
| import { useUserAppStore } from '../store'; | ||||
| import { useEffect } from 'react'; | ||||
| import { Button, Form, Input, Modal } from 'antd'; | ||||
| import { PlusOutlined } from '@ant-design/icons'; | ||||
| import { isObjectNull } from '@/utils/is-null'; | ||||
| import { useNavigate } from 'react-router'; | ||||
| import { FileUpload } from '../modules/FileUpload'; | ||||
| const FormModal = () => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const containerStore = useUserAppStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         showEdit: state.showEdit, | ||||
|         setShowEdit: state.setShowEdit, | ||||
|         formData: state.formData, | ||||
|         updateData: state.updateData, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   useEffect(() => { | ||||
|     const open = containerStore.showEdit; | ||||
|     if (open) { | ||||
|       if (open) { | ||||
|         const isNull = isObjectNull(containerStore.formData); | ||||
|         if (isNull) { | ||||
|           form.setFieldsValue({}); | ||||
|         } else form.setFieldsValue(containerStore.formData); | ||||
|       } | ||||
|     } | ||||
|   }, [containerStore.showEdit]); | ||||
|   const onFinish = async (values: any) => { | ||||
|     containerStore.updateData(values); | ||||
|   }; | ||||
|   const onClose = () => { | ||||
|     containerStore.setShowEdit(false); | ||||
|     form.resetFields(); | ||||
|   }; | ||||
|   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='key' label='key'> | ||||
|           <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> | ||||
|     </Modal> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const List = () => { | ||||
|   const userAppStore = useUserAppStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         list: state.list, | ||||
|         getList: state.getList, | ||||
|         setShowEdit: state.setShowEdit, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   const navicate = useNavigate(); | ||||
|   useEffect(() => { | ||||
|     userAppStore.getList(); | ||||
|   }, []); | ||||
|   return ( | ||||
|     <div className='w-full h-full flex bg-slate-100'> | ||||
|       <div className='p-2 h-full bg-white'> | ||||
|         <Button | ||||
|           onClick={() => { | ||||
|             userAppStore.setShowEdit(true); | ||||
|           }} | ||||
|           icon={<PlusOutlined />}></Button> | ||||
|       </div> | ||||
|       <div className='flex-grow'> | ||||
|         <div className='w-full h-full p-4'> | ||||
|           <div className='w-full h-full bg-white rounded-lg p-2'> | ||||
|             <div className='flex flex-wrap gap-2'> | ||||
|               {userAppStore.list.map((item) => { | ||||
|                 return ( | ||||
|                   <div | ||||
|                     className='card border-t w-[300px] ' | ||||
|                     key={item.id} | ||||
|                     onClick={() => { | ||||
|                       navicate(`/app/${item.key}/verison/list`); | ||||
|                     }}> | ||||
|                     <div className='card-title'>{item.title}</div> | ||||
|                   </div> | ||||
|                 ); | ||||
|               })} | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <FormModal /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										15
									
								
								src/pages/app/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/pages/app/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import { Navigate, Route, Routes } from 'react-router-dom'; | ||||
| import { Main } from './layouts'; | ||||
| import { List } from './edit/List'; | ||||
| import { AppVersionList } from './edit/AppVersionList'; | ||||
| export const App = () => { | ||||
|   return ( | ||||
|     <Routes> | ||||
|       <Route element={<Main />}> | ||||
|         <Route path='/' element={<Navigate to='/app/edit/list' />}></Route> | ||||
|         <Route path='edit/list' element={<List />} /> | ||||
|         <Route path='/:appKey/verison/list' element={<AppVersionList />} /> | ||||
|       </Route> | ||||
|     </Routes> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										5
									
								
								src/pages/app/layouts/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/pages/app/layouts/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import { LayoutMain } from '@/modules/layout'; | ||||
|  | ||||
| export const Main = () => { | ||||
|   return <LayoutMain title='User Apps' />; | ||||
| }; | ||||
							
								
								
									
										56
									
								
								src/pages/app/modules/FileUpload.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/pages/app/modules/FileUpload.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| export type FileType = { | ||||
|   name: string; | ||||
|   size: number; | ||||
|   lastModified: number; | ||||
|   webkitRelativePath: string; // 包含name | ||||
| }; | ||||
|  | ||||
| export const FileUpload = () => { | ||||
|   const onChange = async (e) => { | ||||
|     console.log(e.target.files); | ||||
|     // webkitRelativePath | ||||
|     let files = Array.from(e.target.files) as any[]; | ||||
|     console.log(files); | ||||
|     // 过滤 文件 .DS_Store | ||||
|     files = files.filter((file) => { | ||||
|       if (file.webkitRelativePath.startsWith('__MACOSX')) { | ||||
|         return false; | ||||
|       } | ||||
|       return !file.name.startsWith('.'); | ||||
|     }); | ||||
|     if (files.length === 0) { | ||||
|       console.log('no files'); | ||||
|       return; | ||||
|     } | ||||
|     const root = files[0].webkitRelativePath.split('/')[0]; | ||||
|     const formData = new FormData(); | ||||
|     files.forEach((file) => { | ||||
|       // relativePath 去除第一级 | ||||
|       const webkitRelativePath = file.webkitRelativePath.replace(root + '/', ''); | ||||
|       formData.append('file', file, webkitRelativePath); // 保留文件夹路径 | ||||
|     }); | ||||
|     formData.append('appKey','codeflow'); | ||||
|     formData.append('version', '0.0.2'); | ||||
|     const res = await fetch('/api/app/upload', { | ||||
|       method: 'POST', | ||||
|       body: formData,// | ||||
|       headers: { | ||||
|         Authorization: 'Bearer ' + localStorage.getItem('token'), | ||||
|       }, | ||||
|     }); | ||||
|     console.log(res); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|       文件上传: | ||||
|       <input | ||||
|         type='file' | ||||
|         // @ts-ignore | ||||
|         webkitdirectory='true' | ||||
|         multiple | ||||
|         onChange={onChange} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										78
									
								
								src/pages/app/store/app-version.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/pages/app/store/app-version.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| import { create } from 'zustand'; | ||||
| import { query } from '@/modules'; | ||||
| import { message } from 'antd'; | ||||
|  | ||||
| type AppVersionStore = { | ||||
|   showEdit: boolean; | ||||
|   setShowEdit: (showEdit: boolean) => void; | ||||
|   formData: any; | ||||
|   setFormData: (formData: any) => void; | ||||
|   loading: boolean; | ||||
|   setLoading: (loading: boolean) => void; | ||||
|   key: string; | ||||
|   setKey: (key: string) => void; | ||||
|   list: any[]; | ||||
|   getList: () => Promise<void>; | ||||
|   updateData: (data: any) => Promise<void>; | ||||
|   deleteData: (id: string) => Promise<void>; | ||||
| }; | ||||
| export const useAppVersionStore = create<AppVersionStore>((set, get) => { | ||||
|   return { | ||||
|     showEdit: false, | ||||
|     setShowEdit: (showEdit) => set({ showEdit }), | ||||
|     formData: {}, | ||||
|     setFormData: (formData) => set({ formData }), | ||||
|     loading: false, | ||||
|     setLoading: (loading) => set({ loading }), | ||||
|     key: '', | ||||
|     setKey: (key) => set({ key }), | ||||
|     list: [], | ||||
|     getList: async () => { | ||||
|       set({ loading: true }); | ||||
|       const key = get().key; | ||||
|  | ||||
|       const res = await query.post({ | ||||
|         path: 'app', | ||||
|         key: 'list', | ||||
|         data: { | ||||
|           key, | ||||
|         }, | ||||
|       }); | ||||
|       set({ loading: false }); | ||||
|       if (res.code === 200) { | ||||
|         set({ list: res.data }); | ||||
|       } else { | ||||
|         message.error(res.message || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     updateData: async (data) => { | ||||
|       const { getList } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'app', | ||||
|         key: 'update', | ||||
|         data, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         message.success('Success'); | ||||
|         set({ showEdit: false, formData: res.data }); | ||||
|         getList(); | ||||
|       } else { | ||||
|         message.error(res.message || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     deleteData: async (id) => { | ||||
|       const { getList } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'app', | ||||
|         key: 'delete', | ||||
|         id, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         getList(); | ||||
|         message.success('Success'); | ||||
|       } else { | ||||
|         message.error(res.message || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| }); | ||||
							
								
								
									
										2
									
								
								src/pages/app/store/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/pages/app/store/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| export * from './user-app'; | ||||
| export * from './app-version'; | ||||
							
								
								
									
										69
									
								
								src/pages/app/store/user-app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/pages/app/store/user-app.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import { create } from 'zustand'; | ||||
| import { query } from '@/modules'; | ||||
| import { message } from 'antd'; | ||||
| type UserAppStore = { | ||||
|   showEdit: boolean; | ||||
|   setShowEdit: (showEdit: boolean) => void; | ||||
|   formData: any; | ||||
|   setFormData: (formData: any) => void; | ||||
|   loading: boolean; | ||||
|   setLoading: (loading: boolean) => void; | ||||
|   list: any[]; | ||||
|   getList: () => Promise<void>; | ||||
|   updateData: (data: any) => Promise<void>; | ||||
|   deleteData: (id: string) => Promise<void>; | ||||
| }; | ||||
| export const useUserAppStore = create<UserAppStore>((set, get) => { | ||||
|   return { | ||||
|     showEdit: false, | ||||
|     setShowEdit: (showEdit) => set({ showEdit }), | ||||
|     formData: {}, | ||||
|     setFormData: (formData) => set({ formData }), | ||||
|     loading: false, | ||||
|     setLoading: (loading) => set({ loading }), | ||||
|     list: [], | ||||
|     getList: async () => { | ||||
|       set({ loading: true }); | ||||
|  | ||||
|       const res = await query.post({ | ||||
|         path: 'user-app', | ||||
|         key: 'list', | ||||
|       }); | ||||
|       set({ loading: false }); | ||||
|       if (res.code === 200) { | ||||
|         set({ list: res.data }); | ||||
|       } else { | ||||
|         message.error(res.message || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     updateData: async (data) => { | ||||
|       const { getList } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'user-app', | ||||
|         key: 'update', | ||||
|         data, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         message.success('Success'); | ||||
|         set({ showEdit: false, formData: res.data }); | ||||
|         getList(); | ||||
|       } else { | ||||
|         message.error(res.message || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     deleteData: async (id) => { | ||||
|       const { getList } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'user-app', | ||||
|         key: 'delete', | ||||
|         id, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         getList(); | ||||
|         message.success('Success'); | ||||
|       } else { | ||||
|         message.error(res.message || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user