mirror of
				https://github.com/PlayEdu/backend
				synced 2025-10-26 23:14:45 +08:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			895385c4d7
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b49c73f338 | ||
|  | 6ccf595984 | ||
|  | 7c0decf82a | ||
|  | 97c5540d96 | ||
|  | c94c5106e5 | ||
|  | 5670e23d27 | ||
|  | 65ed275964 | ||
|  | 015dc667cb | ||
|  | 6ecd1de62a | ||
|  | b257fa8e30 | ||
|  | 09d2051c19 | ||
|  | 1fc70f3494 | ||
|  | 123c19126a | ||
|  | 40e1dd03ad | ||
|  | 6ed4d50006 | ||
|  | fbf95bbb66 | ||
|  | 9534877350 | ||
|  | 7820448c0d | 
| @@ -13,10 +13,5 @@ | ||||
|   <body> | ||||
|     <div id="root"></div> | ||||
|     <script type="module" src="/src/main.tsx"></script> | ||||
|     <script | ||||
|       crossorigin="anonymous" | ||||
|       integrity="sha512-oHrfR/z2wkuRuaHrdZ9NhoT/o/1kteub+QvmQgVzOKK7NTvIKQMvnY9+/RR0+eW311o4lAE/YzzLXXmP2XUvig==" | ||||
|       src="https://lib.baomitu.com/hls.js/1.1.4/hls.min.js" | ||||
|     ></script> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "frontend", | ||||
|   "private": true, | ||||
|   "version": "0.0.0", | ||||
|   "name": "playedu-admin-interface", | ||||
|   "private": false, | ||||
|   "version": "1.6.0", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
| @@ -14,6 +14,7 @@ | ||||
|     "ahooks": "^3.7.6", | ||||
|     "antd": "^5.3.2", | ||||
|     "axios": "^1.3.4", | ||||
|     "dayjs": "^1.11.10", | ||||
|     "echarts": "^5.4.2", | ||||
|     "localforage": "^1.10.0", | ||||
|     "match-sorter": "^6.3.1", | ||||
| @@ -24,12 +25,11 @@ | ||||
|     "react-router-dom": "^6.9.0", | ||||
|     "redux": "^4.2.1", | ||||
|     "sort-by": "^1.2.0", | ||||
|     "web-vitals": "^3.3.0", | ||||
|     "xlsx": "^0.18.5" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/react": "^18.0.28", | ||||
|     "@types/react-dom": "^18.0.11", | ||||
|     "@types/react": "^18.2.0", | ||||
|     "@types/react-dom": "^18.2.0", | ||||
|     "@vitejs/plugin-react-swc": "^3.0.0", | ||||
|     "less": "^4.1.3", | ||||
|     "rollup-plugin-gzip": "^3.1.0", | ||||
|   | ||||
							
								
								
									
										1
									
								
								public/js/xg/hls.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								public/js/xg/hls.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/assets/images/commen/upload.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/images/commen/upload.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 919 B | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 64 KiB | 
| @@ -14,7 +14,7 @@ export const BackBartment = (props: PropInterface) => { | ||||
|   return ( | ||||
|     <div className={styles["back-bar-box"]}> | ||||
|       <Button | ||||
|       style={{paddingLeft:0}} | ||||
|         style={{ paddingLeft: 0 }} | ||||
|         icon={<LeftOutlined />} | ||||
|         type="link" | ||||
|         danger | ||||
|   | ||||
| @@ -17,12 +17,26 @@ export const Footer: React.FC<PropInterface> = ({ type }) => { | ||||
|         textAlign: "center", | ||||
|       }} | ||||
|     > | ||||
|       <Link to="https://playedu.xyz/" target="blank"> | ||||
|       <Link | ||||
|         to="https://playedu.xyz/" | ||||
|         style={{ | ||||
|           display: "flex", | ||||
|           alignItems: "center", | ||||
|           justifyContent: "center", | ||||
|         }} | ||||
|         target="blank" | ||||
|       > | ||||
|         <i | ||||
|           style={{ fontSize: 30, color: "#cccccc" }} | ||||
|           className="iconfont icon-waterprint footer-icon" | ||||
|           onClick={() => {}} | ||||
|         ></i> | ||||
|         <span | ||||
|           className="ml-5" | ||||
|           style={{ color: "#D7D7D7", fontSize: 12, marginTop: -5 }} | ||||
|         > | ||||
|           Version 1.6 | ||||
|         </span> | ||||
|       </Link> | ||||
|     </Layout.Footer> | ||||
|   ); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { Row, Col, Empty, Table, Spin, Typography, Input, Button } from "antd"; | ||||
| import type { ColumnsType } from "antd/es/table"; | ||||
| import { resource } from "../../api"; | ||||
| import styles from "./index.module.less"; | ||||
| import { TreeCategory, UploadCoursewareButton } from "../../compenents"; | ||||
| import { TreeCategory } from "../../compenents"; | ||||
|  | ||||
| interface VideoItem { | ||||
|   id: number; | ||||
| @@ -181,12 +181,7 @@ export const UploadCoursewareSub = (props: PropsInterface) => { | ||||
|         <Col span={17}> | ||||
|           <Row style={{ marginBottom: 24, paddingLeft: 10 }}> | ||||
|             <div className="float-left  j-b-flex"> | ||||
|               <UploadCoursewareButton | ||||
|                 categoryIds={category_ids} | ||||
|                 onUpdate={() => { | ||||
|                   resetVideoList(); | ||||
|                 }} | ||||
|               ></UploadCoursewareButton> | ||||
|               <div className="d-flex"></div> | ||||
|               <div className="d-flex"> | ||||
|                 <div className="d-flex mr-24"> | ||||
|                   <Typography.Text>名称:</Typography.Text> | ||||
|   | ||||
| @@ -1,20 +1,8 @@ | ||||
| import { InboxOutlined } from "@ant-design/icons"; | ||||
| import { | ||||
|   Button, | ||||
|   Col, | ||||
|   message, | ||||
|   Modal, | ||||
|   Progress, | ||||
|   Row, | ||||
|   Table, | ||||
|   Tag, | ||||
|   Upload, | ||||
| } from "antd"; | ||||
| import Dragger from "antd/es/upload/Dragger"; | ||||
| import { useRef, useState } from "react"; | ||||
| import { generateUUID, parseVideo } from "../../utils"; | ||||
| import { minioMergeVideo, minioUploadId } from "../../api/upload"; | ||||
| import { UploadChunk } from "../../js/minio-upload-chunk"; | ||||
| import { useEffect, useRef, useState } from "react"; | ||||
| import { useSelector } from "react-redux"; | ||||
| import { Button } from "antd"; | ||||
| import { useDispatch } from "react-redux"; | ||||
| import { uploadAction } from "../../store/user/loginUserSlice"; | ||||
|  | ||||
| interface PropsInterface { | ||||
|   categoryIds: number[]; | ||||
| @@ -22,200 +10,32 @@ interface PropsInterface { | ||||
| } | ||||
|  | ||||
| export const UploadVideoButton = (props: PropsInterface) => { | ||||
|   const [showModal, setShowModal] = useState(false); | ||||
|   const localFileList = useRef<FileItem[]>([]); | ||||
|   const [fileList, setFileList] = useState<FileItem[]>([]); | ||||
|   const dispatch = useDispatch(); | ||||
|   const uploadStatus = useSelector( | ||||
|     (state: any) => state.loginUser.value.uploadStatus | ||||
|   ); | ||||
|  | ||||
|   const getMinioUploadId = async () => { | ||||
|     let resp: any = await minioUploadId("mp4"); | ||||
|     return resp.data; | ||||
|   }; | ||||
|  | ||||
|   const uploadProps = { | ||||
|     multiple: true, | ||||
|     beforeUpload: async (file: File) => { | ||||
|       if (file.type === "video/mp4") { | ||||
|         // 视频封面解析 || 视频时长解析 | ||||
|         let videoInfo = await parseVideo(file); | ||||
|         // 添加到本地待上传 | ||||
|         let data = await getMinioUploadId(); | ||||
|         let run = new UploadChunk(file, data["upload_id"], data["filename"]); | ||||
|         let item: FileItem = { | ||||
|           id: generateUUID(), | ||||
|           file: file, | ||||
|           upload: { | ||||
|             handler: run, | ||||
|             progress: 0, | ||||
|             status: 0, | ||||
|             remark: "", | ||||
|           }, | ||||
|           video: { | ||||
|             duration: videoInfo.duration, | ||||
|             poster: videoInfo.poster, | ||||
|           }, | ||||
|         }; | ||||
|         item.upload.handler.on("success", () => { | ||||
|           minioMergeVideo( | ||||
|             data["filename"], | ||||
|             data["upload_id"], | ||||
|             props.categoryIds.join(","), | ||||
|             item.file.name, | ||||
|             "mp4", | ||||
|             item.file.size, | ||||
|             item.video?.duration || 0, | ||||
|             item.video?.poster || "" | ||||
|           ).then(() => { | ||||
|             item.upload.progress = 100; | ||||
|             item.upload.status = item.upload.handler.getUploadStatus(); | ||||
|             setFileList([...localFileList.current]); | ||||
|           }); | ||||
|         }); | ||||
|         item.upload.handler.on("progress", (p: number) => { | ||||
|           item.upload.status = item.upload.handler.getUploadStatus(); | ||||
|           item.upload.progress = p >= 100 ? 99 : p; | ||||
|           setFileList([...localFileList.current]); | ||||
|         }); | ||||
|         item.upload.handler.on("error", (msg: string) => { | ||||
|           item.upload.status = item.upload.handler.getUploadStatus(); | ||||
|           item.upload.remark = msg; | ||||
|           setFileList([...localFileList.current]); | ||||
|         }); | ||||
|         item.upload.handler.start(); | ||||
|         // 先插入到ref | ||||
|         localFileList.current.push(item); | ||||
|         // 再更新list | ||||
|         setFileList([...localFileList.current]); | ||||
|       } else { | ||||
|         message.error(`${file.name} 并不是 mp4 视频文件`); | ||||
|       } | ||||
|       return Upload.LIST_IGNORE; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   const closeWin = () => { | ||||
|     if (fileList.length > 0) { | ||||
|       fileList.forEach((item) => { | ||||
|         if (item.upload.status !== 5 && item.upload.status !== 7) { | ||||
|           item.upload.handler.cancel(); | ||||
|         } | ||||
|       }); | ||||
|   useEffect(() => { | ||||
|     if (!uploadStatus) { | ||||
|       props.onUpdate(); | ||||
|     } | ||||
|     props.onUpdate(); | ||||
|     localFileList.current = []; | ||||
|     setFileList([]); | ||||
|     setShowModal(false); | ||||
|   }; | ||||
|   }, [uploadStatus]); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Button | ||||
|         type="primary" | ||||
|         onClick={() => { | ||||
|           setShowModal(true); | ||||
|           dispatch( | ||||
|             uploadAction({ | ||||
|               uploadStatus: true, | ||||
|               uploadCateIds: props.categoryIds, | ||||
|             }) | ||||
|           ); | ||||
|         }} | ||||
|       > | ||||
|         上传视频 | ||||
|       </Button> | ||||
|  | ||||
|       {showModal ? ( | ||||
|         <Modal | ||||
|           width={800} | ||||
|           title="上传视频" | ||||
|           open={true} | ||||
|           onCancel={() => { | ||||
|             closeWin(); | ||||
|           }} | ||||
|           maskClosable={false} | ||||
|           closable={false} | ||||
|           onOk={() => { | ||||
|             closeWin(); | ||||
|           }} | ||||
|           okText="完成" | ||||
|         > | ||||
|           <Row gutter={[0, 10]}> | ||||
|             <Col span={24}> | ||||
|               <Dragger {...uploadProps}> | ||||
|                 <p className="ant-upload-drag-icon"> | ||||
|                   <InboxOutlined /> | ||||
|                 </p> | ||||
|                 <p className="ant-upload-text">请将视频拖拽到此处上传</p> | ||||
|                 <p className="ant-upload-hint"> | ||||
|                   支持一次上传多个 / 支持 mp4 格式视频 | ||||
|                 </p> | ||||
|               </Dragger> | ||||
|             </Col> | ||||
|             <Col span={24}> | ||||
|               <Table | ||||
|                 pagination={false} | ||||
|                 rowKey="id" | ||||
|                 columns={[ | ||||
|                   { | ||||
|                     title: "视频", | ||||
|                     dataIndex: "name", | ||||
|                     key: "name", | ||||
|                     render: (_, record) => <span>{record.file.name}</span>, | ||||
|                   }, | ||||
|                   { | ||||
|                     title: "大小", | ||||
|                     dataIndex: "size", | ||||
|                     key: "size", | ||||
|                     render: (_, record) => ( | ||||
|                       <span> | ||||
|                         {(record.file.size / 1024 / 1024).toFixed(2)}M | ||||
|                       </span> | ||||
|                     ), | ||||
|                   }, | ||||
|                   { | ||||
|                     title: "进度", | ||||
|                     dataIndex: "progress", | ||||
|                     key: "progress", | ||||
|                     render: (_, record: FileItem) => ( | ||||
|                       <> | ||||
|                         {record.upload.status === 0 ? ( | ||||
|                           "等待上传" | ||||
|                         ) : ( | ||||
|                           <Progress | ||||
|                             size="small" | ||||
|                             steps={20} | ||||
|                             percent={record.upload.progress} | ||||
|                           /> | ||||
|                         )} | ||||
|                       </> | ||||
|                     ), | ||||
|                   }, | ||||
|                   { | ||||
|                     title: "操作", | ||||
|                     key: "action", | ||||
|                     render: (_, record) => ( | ||||
|                       <> | ||||
|                         {record.upload.status === 5 ? ( | ||||
|                           <> | ||||
|                             <Button | ||||
|                               type="link" | ||||
|                               size="small" | ||||
|                               className="b-n-link c-red" | ||||
|                               onClick={() => { | ||||
|                                 record.upload.handler.retry(); | ||||
|                               }} | ||||
|                             > | ||||
|                               失败.重试 | ||||
|                             </Button> | ||||
|                           </> | ||||
|                         ) : null} | ||||
|  | ||||
|                         {record.upload.status === 7 ? ( | ||||
|                           <Tag color="success">上传成功</Tag> | ||||
|                         ) : null} | ||||
|                       </> | ||||
|                     ), | ||||
|                   }, | ||||
|                 ]} | ||||
|                 dataSource={fileList} | ||||
|               /> | ||||
|             </Col> | ||||
|           </Row> | ||||
|         </Modal> | ||||
|       ) : null} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										26
									
								
								src/compenents/upload-video-float-button/index.module.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/compenents/upload-video-float-button/index.module.less
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| .float-button { | ||||
|   width: auto; | ||||
|   height: 32px; | ||||
|   background: #ffffff; | ||||
|   box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.1); | ||||
|   border-radius: 16px; | ||||
|   box-sizing: border-box; | ||||
|   padding: 6px 8px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: fixed; | ||||
|   right: 24px; | ||||
|   bottom: 20px; | ||||
|   z-index: 50; | ||||
|   cursor: pointer; | ||||
|   img { | ||||
|     width: 20px; | ||||
|     height: 20px; | ||||
|   } | ||||
|   span { | ||||
|     margin-left: 6px; | ||||
|     font-size: 12px; | ||||
|     font-weight: 400; | ||||
|     color: #ff4d4f; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										289
									
								
								src/compenents/upload-video-float-button/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								src/compenents/upload-video-float-button/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | ||||
| import { InboxOutlined } from "@ant-design/icons"; | ||||
| import { | ||||
|   Button, | ||||
|   Col, | ||||
|   message, | ||||
|   Modal, | ||||
|   Progress, | ||||
|   Row, | ||||
|   Table, | ||||
|   Tag, | ||||
|   Upload, | ||||
| } from "antd"; | ||||
| import Dragger from "antd/es/upload/Dragger"; | ||||
| import { useEffect, useRef, useState } from "react"; | ||||
| import { generateUUID, parseVideo } from "../../utils"; | ||||
| import styles from "./index.module.less"; | ||||
| import { minioMergeVideo, minioUploadId } from "../../api/upload"; | ||||
| import { UploadChunk } from "../../js/minio-upload-chunk"; | ||||
| import { useDispatch } from "react-redux"; | ||||
| import { useSelector } from "react-redux"; | ||||
| import { uploadAction } from "../../store/user/loginUserSlice"; | ||||
| import upIcon from "../../assets/images/commen/upload.png"; | ||||
|  | ||||
| export const UploadVideoFloatButton = () => { | ||||
|   const dispatch = useDispatch(); | ||||
|   const [showModal, setShowModal] = useState(false); | ||||
|   const localFileList = useRef<FileItem[]>([]); | ||||
|   const intervalId = useRef<number>(); | ||||
|   const intervalId2 = useRef<number>(); | ||||
|   const [successNum, setSuccessNum] = useState(0); | ||||
|   const [fileList, setFileList] = useState<FileItem[]>([]); | ||||
|   const uploadStatus = useSelector( | ||||
|     (state: any) => state.loginUser.value.uploadStatus | ||||
|   ); | ||||
|   const categoryIds = useSelector( | ||||
|     (state: any) => state.loginUser.value.uploadCateIds | ||||
|   ); | ||||
|  | ||||
|   const getMinioUploadId = async () => { | ||||
|     let resp: any = await minioUploadId("mp4"); | ||||
|     return resp.data; | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (uploadStatus) { | ||||
|       setShowModal(true); | ||||
|       intervalId.current = setInterval(() => { | ||||
|         let num = localFileList.current.filter( | ||||
|           (it) => it.upload.status === 7 | ||||
|         ).length; | ||||
|         setSuccessNum(num); | ||||
|       }, 5000); | ||||
|       let timeDiv = document.createElement("div"); | ||||
|       document.body.appendChild(timeDiv); | ||||
|       intervalId2.current = setInterval(() => { | ||||
|         timeDiv && timeDiv.click(); | ||||
|       }, 10000); | ||||
|     } else { | ||||
|       window.clearInterval(intervalId.current); | ||||
|       window.clearInterval(intervalId2.current); | ||||
|       console.log("定时器已销毁"); | ||||
|     } | ||||
|   }, [uploadStatus]); | ||||
|  | ||||
|   const uploadProps = { | ||||
|     multiple: true, | ||||
|     beforeUpload: async (file: File, fileList: any) => { | ||||
|       if (file.size > 2 * 1024 * 1024 * 1024) { | ||||
|         message.error(`${file.name} 大小超过2G`); | ||||
|         return Upload.LIST_IGNORE; | ||||
|       } | ||||
|       if (fileList.length > 10) { | ||||
|         message.config({ maxCount: 1 }); | ||||
|         message.error("单次最多上传10个视频"); | ||||
|         return Upload.LIST_IGNORE; | ||||
|       } else { | ||||
|         message.config({ maxCount: 10 }); | ||||
|       } | ||||
|       if (file.type === "video/mp4") { | ||||
|         // 视频封面解析 || 视频时长解析 | ||||
|         let videoInfo = await parseVideo(file); | ||||
|         // 添加到本地待上传 | ||||
|         let data = await getMinioUploadId(); | ||||
|         let run = new UploadChunk(file, data["upload_id"], data["filename"]); | ||||
|         let item: FileItem = { | ||||
|           id: generateUUID(), | ||||
|           file: file, | ||||
|           upload: { | ||||
|             handler: run, | ||||
|             progress: 0, | ||||
|             status: 0, | ||||
|             remark: "", | ||||
|           }, | ||||
|           video: { | ||||
|             duration: videoInfo.duration, | ||||
|             poster: videoInfo.poster, | ||||
|           }, | ||||
|         }; | ||||
|         item.upload.handler.on("success", () => { | ||||
|           minioMergeVideo( | ||||
|             data["filename"], | ||||
|             data["upload_id"], | ||||
|             categoryIds.join(","), | ||||
|             item.file.name, | ||||
|             "mp4", | ||||
|             item.file.size, | ||||
|             item.video?.duration || 0, | ||||
|             item.video?.poster || "" | ||||
|           ).then(() => { | ||||
|             item.upload.progress = 100; | ||||
|             item.upload.status = item.upload.handler.getUploadStatus(); | ||||
|             setFileList([...localFileList.current]); | ||||
|           }); | ||||
|         }); | ||||
|         item.upload.handler.on("progress", (p: number) => { | ||||
|           item.upload.status = item.upload.handler.getUploadStatus(); | ||||
|           item.upload.progress = p >= 100 ? 99 : p; | ||||
|           setFileList([...localFileList.current]); | ||||
|         }); | ||||
|         item.upload.handler.on("error", (msg: string) => { | ||||
|           item.upload.status = item.upload.handler.getUploadStatus(); | ||||
|           item.upload.remark = msg; | ||||
|           setFileList([...localFileList.current]); | ||||
|         }); | ||||
|         item.upload.handler.start(); | ||||
|         // 先插入到ref | ||||
|         localFileList.current.push(item); | ||||
|         // 再更新list | ||||
|         setFileList([...localFileList.current]); | ||||
|       } else { | ||||
|         message.error(`${file.name} 并不是 mp4 视频文件`); | ||||
|       } | ||||
|       return Upload.LIST_IGNORE; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   const closeWin = () => { | ||||
|     if (fileList.length > 0) { | ||||
|       fileList.forEach((item) => { | ||||
|         if (item.upload.status !== 5 && item.upload.status !== 7) { | ||||
|           item.upload.handler.cancel(); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|     localFileList.current = []; | ||||
|     setFileList([]); | ||||
|     setSuccessNum(0); | ||||
|     setShowModal(false); | ||||
|     dispatch( | ||||
|       uploadAction({ | ||||
|         uploadStatus: false, | ||||
|         uploadCateIds: [], | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {uploadStatus ? ( | ||||
|         <> | ||||
|           <div | ||||
|             style={{ display: showModal ? "none" : "flex" }} | ||||
|             className={styles["float-button"]} | ||||
|             onClick={() => setShowModal(true)} | ||||
|           > | ||||
|             <img src={upIcon} /> | ||||
|             <span> | ||||
|               视频上传成功 ({successNum}/{fileList.length}) | ||||
|             </span> | ||||
|           </div> | ||||
|           <Modal | ||||
|             width={800} | ||||
|             title="上传视频" | ||||
|             open={showModal} | ||||
|             maskClosable={false} | ||||
|             footer={null} | ||||
|             onCancel={() => { | ||||
|               closeWin(); | ||||
|             }} | ||||
|           > | ||||
|             <Row gutter={[0, 10]}> | ||||
|               <Col span={24}> | ||||
|                 <Dragger {...uploadProps}> | ||||
|                   <p className="ant-upload-drag-icon"> | ||||
|                     <InboxOutlined /> | ||||
|                   </p> | ||||
|                   <p className="ant-upload-text">请将视频拖拽到此处上传</p> | ||||
|                   <p className="ant-upload-hint"> | ||||
|                     支持一次上传多个 / 支持2G以内的mp4文件 | ||||
|                   </p> | ||||
|                 </Dragger> | ||||
|               </Col> | ||||
|               <Col span={24}> | ||||
|                 <Table | ||||
|                   pagination={false} | ||||
|                   rowKey="id" | ||||
|                   columns={[ | ||||
|                     { | ||||
|                       title: "视频", | ||||
|                       dataIndex: "name", | ||||
|                       key: "name", | ||||
|                       render: (_, record) => <span>{record.file.name}</span>, | ||||
|                     }, | ||||
|                     { | ||||
|                       title: "大小", | ||||
|                       dataIndex: "size", | ||||
|                       key: "size", | ||||
|                       render: (_, record) => ( | ||||
|                         <span> | ||||
|                           {(record.file.size / 1024 / 1024).toFixed(2)}M | ||||
|                         </span> | ||||
|                       ), | ||||
|                     }, | ||||
|                     { | ||||
|                       title: "进度", | ||||
|                       dataIndex: "progress", | ||||
|                       key: "progress", | ||||
|                       render: (_, record: FileItem) => ( | ||||
|                         <> | ||||
|                           {record.upload.status === 0 ? ( | ||||
|                             "等待上传" | ||||
|                           ) : ( | ||||
|                             <Progress | ||||
|                               size="small" | ||||
|                               steps={20} | ||||
|                               percent={record.upload.progress} | ||||
|                             /> | ||||
|                           )} | ||||
|                         </> | ||||
|                       ), | ||||
|                     }, | ||||
|                     { | ||||
|                       title: "操作", | ||||
|                       key: "action", | ||||
|                       render: (_, record) => ( | ||||
|                         <> | ||||
|                           {record.upload.status === 5 ? ( | ||||
|                             <> | ||||
|                               <Button | ||||
|                                 type="link" | ||||
|                                 size="small" | ||||
|                                 className="b-n-link c-red" | ||||
|                                 onClick={() => { | ||||
|                                   record.upload.handler.retry(); | ||||
|                                 }} | ||||
|                               > | ||||
|                                 失败.重试 | ||||
|                               </Button> | ||||
|                             </> | ||||
|                           ) : null} | ||||
|  | ||||
|                           {record.upload.status === 7 ? ( | ||||
|                             <Tag color="success">上传成功</Tag> | ||||
|                           ) : null} | ||||
|                         </> | ||||
|                       ), | ||||
|                     }, | ||||
|                   ]} | ||||
|                   dataSource={fileList} | ||||
|                 /> | ||||
|               </Col> | ||||
|               <Col span={24}> | ||||
|                 <div className="r-r-flex"> | ||||
|                   <Button | ||||
|                     type="primary" | ||||
|                     onClick={() => { | ||||
|                       closeWin(); | ||||
|                     }} | ||||
|                   > | ||||
|                     完成 | ||||
|                   </Button> | ||||
|                   <Button | ||||
|                     type="default" | ||||
|                     className="mr-16" | ||||
|                     onClick={() => { | ||||
|                       setShowModal(false); | ||||
|                     }} | ||||
|                   > | ||||
|                     最小化 | ||||
|                   </Button> | ||||
|                 </div> | ||||
|               </Col> | ||||
|             </Row> | ||||
|           </Modal> | ||||
|         </> | ||||
|       ) : null} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -3,7 +3,6 @@ import { Row, Col, Empty, Table, Spin, Typography, Input, Button } from "antd"; | ||||
| import type { ColumnsType } from "antd/es/table"; | ||||
| import { resource } from "../../api"; | ||||
| import styles from "./index.module.less"; | ||||
| import { UploadVideoButton } from "../upload-video-button"; | ||||
| import { DurationText, TreeCategory } from "../../compenents"; | ||||
|  | ||||
| interface VideoItem { | ||||
| @@ -173,12 +172,7 @@ export const UploadVideoSub = (props: PropsInterface) => { | ||||
|         <Col span={17}> | ||||
|           <Row style={{ marginBottom: 24, paddingLeft: 10 }}> | ||||
|             <div className="float-left  j-b-flex"> | ||||
|               <UploadVideoButton | ||||
|                 categoryIds={category_ids} | ||||
|                 onUpdate={() => { | ||||
|                   resetVideoList(); | ||||
|                 }} | ||||
|               ></UploadVideoButton> | ||||
|               <div className="d-flex"></div> | ||||
|               <div className="d-flex"> | ||||
|                 <div className="d-flex mr-24"> | ||||
|                   <Typography.Text>名称:</Typography.Text> | ||||
|   | ||||
| @@ -187,6 +187,11 @@ code { | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .r-r-flex { | ||||
|   display: flex; | ||||
|   flex-direction: row-reverse; | ||||
| } | ||||
|  | ||||
| .flex-1 { | ||||
|   flex: 1; | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import ReactDOM from "react-dom/client"; | ||||
| import "./assets/iconfont/iconfont.css"; | ||||
| import "./index.less"; | ||||
| import App from "./App"; | ||||
| import reportWebVitals from "./reportWebVitals"; | ||||
| import { BrowserRouter } from "react-router-dom"; | ||||
| import { Provider } from "react-redux"; | ||||
| import store from "./store"; | ||||
| @@ -25,8 +24,3 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( | ||||
|     </ConfigProvider> | ||||
|   </Provider> | ||||
| ); | ||||
|  | ||||
| // If you want to start measuring performance in your app, pass a function | ||||
| // to log results (for example: reportWebVitals(console.log)) | ||||
| // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | ||||
| reportWebVitals(); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { | ||||
|   message, | ||||
|   Image, | ||||
|   TreeSelect, | ||||
|   Spin, | ||||
| } from "antd"; | ||||
| import styles from "./create.module.less"; | ||||
| import { useSelector } from "react-redux"; | ||||
| @@ -52,6 +53,7 @@ export const CourseCreate: React.FC<PropInterface> = ({ | ||||
|   const defaultThumb2 = courseDefaultThumbs[1]; | ||||
|   const defaultThumb3 = courseDefaultThumbs[2]; | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [init, setInit] = useState(true); | ||||
|   const [departments, setDepartments] = useState<Option[]>([]); | ||||
|   const [categories, setCategories] = useState<Option[]>([]); | ||||
|   const [thumb, setThumb] = useState(""); | ||||
| @@ -71,9 +73,9 @@ export const CourseCreate: React.FC<PropInterface> = ({ | ||||
|   const [attachments, setAttachments] = useState<number[]>([]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setInit(true); | ||||
|     if (open) { | ||||
|       getParams(); | ||||
|       getCategory(); | ||||
|       initData(); | ||||
|     } | ||||
|   }, [open, cateIds, depIds]); | ||||
|  | ||||
| @@ -96,43 +98,48 @@ export const CourseCreate: React.FC<PropInterface> = ({ | ||||
|     setShowDrop(false); | ||||
|   }, [form, open]); | ||||
|  | ||||
|   const getParams = () => { | ||||
|     department.departmentList().then((res: any) => { | ||||
|       const departments = res.data.departments; | ||||
|       const departCount: DepIdsModel = res.data.dep_user_count; | ||||
|       if (JSON.stringify(departments) !== "{}") { | ||||
|         const new_arr: any = checkArr(departments, 0, departCount); | ||||
|         setDepartments(new_arr); | ||||
|       } | ||||
|       let type = "open"; | ||||
|       if (depIds.length !== 0 && depIds[0] !== 0) { | ||||
|         type = "elective"; | ||||
|         let item = checkChild(res.data.departments, depIds[0]); | ||||
|         let arr: any[] = []; | ||||
|         if (item === undefined) { | ||||
|           arr.push(depIds[0]); | ||||
|         } else if (item.parent_chain === "") { | ||||
|           arr.push(depIds[0]); | ||||
|         } else { | ||||
|           let new_arr = item.parent_chain.split(","); | ||||
|           new_arr.map((num: any) => { | ||||
|             arr.push(Number(num)); | ||||
|           }); | ||||
|           arr.push(depIds[0]); | ||||
|         } | ||||
|         form.setFieldsValue({ | ||||
|           dep_ids: arr, | ||||
|         }); | ||||
|   const initData = async () => { | ||||
|     await getParams(); | ||||
|     await getCategory(); | ||||
|     setInit(false); | ||||
|   }; | ||||
|  | ||||
|   const getParams = async () => { | ||||
|     let res: any = await department.departmentList(); | ||||
|     const departments = res.data.departments; | ||||
|     const departCount: DepIdsModel = res.data.dep_user_count; | ||||
|     if (JSON.stringify(departments) !== "{}") { | ||||
|       const new_arr: any = checkArr(departments, 0, departCount); | ||||
|       setDepartments(new_arr); | ||||
|     } | ||||
|     let type = "open"; | ||||
|     if (depIds.length !== 0 && depIds[0] !== 0) { | ||||
|       type = "elective"; | ||||
|       let item = checkChild(res.data.departments, depIds[0]); | ||||
|       let arr: any[] = []; | ||||
|       if (item === undefined) { | ||||
|         arr.push(depIds[0]); | ||||
|       } else if (item.parent_chain === "") { | ||||
|         arr.push(depIds[0]); | ||||
|       } else { | ||||
|         form.setFieldsValue({ | ||||
|           dep_ids: depIds, | ||||
|         let new_arr = item.parent_chain.split(","); | ||||
|         new_arr.map((num: any) => { | ||||
|           arr.push(Number(num)); | ||||
|         }); | ||||
|         arr.push(depIds[0]); | ||||
|       } | ||||
|       form.setFieldsValue({ | ||||
|         type: type, | ||||
|         dep_ids: arr, | ||||
|       }); | ||||
|       setType(type); | ||||
|     } else { | ||||
|       form.setFieldsValue({ | ||||
|         dep_ids: depIds, | ||||
|       }); | ||||
|     } | ||||
|     form.setFieldsValue({ | ||||
|       type: type, | ||||
|     }); | ||||
|     setType(type); | ||||
|   }; | ||||
|  | ||||
|   const checkChild = (departments: any[], id: number) => { | ||||
| @@ -145,37 +152,36 @@ export const CourseCreate: React.FC<PropInterface> = ({ | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const getCategory = () => { | ||||
|     course.createCourse().then((res: any) => { | ||||
|       const categories = res.data.categories; | ||||
|       if (JSON.stringify(categories) !== "{}") { | ||||
|         const new_arr: any = checkArr(categories, 0, null); | ||||
|         setCategories(new_arr); | ||||
|       } | ||||
|   const getCategory = async () => { | ||||
|     let res: any = await course.createCourse(); | ||||
|     const categories = res.data.categories; | ||||
|     if (JSON.stringify(categories) !== "{}") { | ||||
|       const new_arr: any = checkArr(categories, 0, null); | ||||
|       setCategories(new_arr); | ||||
|     } | ||||
|  | ||||
|       if (cateIds.length !== 0 && cateIds[0] !== 0) { | ||||
|         let item = checkChild(res.data.categories, cateIds[0]); | ||||
|         let arr: any[] = []; | ||||
|         if (item === undefined) { | ||||
|           arr.push(cateIds[0]); | ||||
|         } else if (item.parent_chain === "") { | ||||
|           arr.push(cateIds[0]); | ||||
|         } else { | ||||
|           let new_arr = item.parent_chain.split(","); | ||||
|           new_arr.map((num: any) => { | ||||
|             arr.push(Number(num)); | ||||
|           }); | ||||
|           arr.push(cateIds[0]); | ||||
|         } | ||||
|         form.setFieldsValue({ | ||||
|           category_ids: arr, | ||||
|         }); | ||||
|     if (cateIds.length !== 0 && cateIds[0] !== 0) { | ||||
|       let item = checkChild(res.data.categories, cateIds[0]); | ||||
|       let arr: any[] = []; | ||||
|       if (item === undefined) { | ||||
|         arr.push(cateIds[0]); | ||||
|       } else if (item.parent_chain === "") { | ||||
|         arr.push(cateIds[0]); | ||||
|       } else { | ||||
|         form.setFieldsValue({ | ||||
|           category_ids: cateIds, | ||||
|         let new_arr = item.parent_chain.split(","); | ||||
|         new_arr.map((num: any) => { | ||||
|           arr.push(Number(num)); | ||||
|         }); | ||||
|         arr.push(cateIds[0]); | ||||
|       } | ||||
|     }); | ||||
|       form.setFieldsValue({ | ||||
|         category_ids: arr, | ||||
|       }); | ||||
|     } else { | ||||
|       form.setFieldsValue({ | ||||
|         category_ids: cateIds, | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const getNewTitle = (title: any, id: number, counts: any) => { | ||||
| @@ -503,7 +509,15 @@ export const CourseCreate: React.FC<PropInterface> = ({ | ||||
|           } | ||||
|           width={634} | ||||
|         > | ||||
|           <div className="float-left mt-24"> | ||||
|           {init && ( | ||||
|             <div className="float-left text-center mt-30"> | ||||
|               <Spin></Spin> | ||||
|             </div> | ||||
|           )} | ||||
|           <div | ||||
|             className="float-left mt-24" | ||||
|             style={{ display: init ? "none" : "block" }} | ||||
|           > | ||||
|             <SelectResource | ||||
|               defaultKeys={ | ||||
|                 chapterType == 0 ? hours : changeChapterHours(chapterHours) | ||||
|   | ||||
| @@ -103,7 +103,6 @@ export const CourseUpdate: React.FC<PropInterface> = ({ | ||||
|       setType(type); | ||||
|       setThumb(res.data.course.thumb); | ||||
|       setInit(false); | ||||
|       console.log(dayjs(res.data.course.published_at, "YYYY-MM-DD HH:mm:ss")); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import React, { useState, useRef, useEffect } from "react"; | ||||
| import { Modal, Form, Input, Cascader, message } from "antd"; | ||||
| import { Modal, Form, Input, Cascader, message, Spin } from "antd"; | ||||
| import styles from "./create.module.less"; | ||||
| import { department } from "../../../api/index"; | ||||
|  | ||||
| @@ -19,11 +19,13 @@ export const DepartmentCreate: React.FC<PropInterface> = ({ | ||||
|   onCancel, | ||||
| }) => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const [init, setInit] = useState(true); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [departments, setDepartments] = useState<any>([]); | ||||
|   const [parent_id, setParentId] = useState<number>(0); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setInit(true); | ||||
|     if (open) { | ||||
|       getParams(); | ||||
|     } | ||||
| @@ -54,6 +56,7 @@ export const DepartmentCreate: React.FC<PropInterface> = ({ | ||||
|         }); | ||||
|         setDepartments(new_arr); | ||||
|       } | ||||
|       setInit(false); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -125,7 +128,15 @@ export const DepartmentCreate: React.FC<PropInterface> = ({ | ||||
|           maskClosable={false} | ||||
|           okButtonProps={{ loading: loading }} | ||||
|         > | ||||
|           <div className="float-left mt-24"> | ||||
|           {init && ( | ||||
|             <div className="float-left text-center mt-30"> | ||||
|               <Spin></Spin> | ||||
|             </div> | ||||
|           )} | ||||
|           <div | ||||
|             className="float-left mt-24" | ||||
|             style={{ display: init ? "none" : "block" }} | ||||
|           > | ||||
|             <Form | ||||
|               form={form} | ||||
|               name="basic" | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import { | ||||
|   SystemConfigStoreInterface, | ||||
|   saveConfigAction, | ||||
| } from "../../store/system/systemConfigSlice"; | ||||
| import { UploadVideoFloatButton } from "../../compenents/upload-video-float-button"; | ||||
|  | ||||
| interface Props { | ||||
|   loginData?: any; | ||||
| @@ -36,6 +37,7 @@ const InitPage = (props: Props) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <Outlet /> | ||||
|       <UploadVideoFloatButton></UploadVideoFloatButton> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -39,9 +39,11 @@ | ||||
|       height: 100%; | ||||
|       box-sizing: border-box; | ||||
|       padding: 90px 10px 80px 30px; | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       .icon { | ||||
|         width: 400px; | ||||
|         height: 400px; | ||||
|         height: auto; | ||||
|       } | ||||
|     } | ||||
|     .right-box { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import React, { useState, useEffect } from "react"; | ||||
| import { Modal, Form, TreeSelect, Input, message } from "antd"; | ||||
| import { Modal, Form, TreeSelect, Input, message, Spin } from "antd"; | ||||
| import styles from "./create.module.less"; | ||||
| import { useSelector } from "react-redux"; | ||||
| import { user, department } from "../../../api/index"; | ||||
| @@ -24,6 +24,7 @@ export const MemberCreate: React.FC<PropInterface> = ({ | ||||
|   onCancel, | ||||
| }) => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const [init, setInit] = useState(true); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [departments, setDepartments] = useState<any>([]); | ||||
|   const memberDefaultAvatar = useSelector( | ||||
| @@ -32,6 +33,7 @@ export const MemberCreate: React.FC<PropInterface> = ({ | ||||
|   const [avatar, setAvatar] = useState<string>(memberDefaultAvatar); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setInit(true); | ||||
|     if (open) { | ||||
|       getParams(); | ||||
|     } | ||||
| @@ -56,6 +58,7 @@ export const MemberCreate: React.FC<PropInterface> = ({ | ||||
|         const new_arr: Option[] = checkArr(departments, 0); | ||||
|         setDepartments(new_arr); | ||||
|       } | ||||
|       setInit(false); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -83,10 +86,10 @@ export const MemberCreate: React.FC<PropInterface> = ({ | ||||
|     if (loading) { | ||||
|       return; | ||||
|     } | ||||
|     if (values.idCard !== "" && !ValidataCredentials(values.idCard)) { | ||||
|       message.error("请输入正确的身份证号!"); | ||||
|       return; | ||||
|     } | ||||
|     // if (values.idCard !== "" && !ValidataCredentials(values.idCard)) { | ||||
|     //   message.error("请输入正确的身份证号!"); | ||||
|     //   return; | ||||
|     // } | ||||
|     setLoading(true); | ||||
|     user | ||||
|       .storeUser( | ||||
| @@ -125,7 +128,15 @@ export const MemberCreate: React.FC<PropInterface> = ({ | ||||
|           maskClosable={false} | ||||
|           okButtonProps={{ loading: loading }} | ||||
|         > | ||||
|           <div className="member-form float-left mt-24"> | ||||
|           {init && ( | ||||
|             <div className="float-left text-center mt-30"> | ||||
|               <Spin></Spin> | ||||
|             </div> | ||||
|           )} | ||||
|           <div | ||||
|             className="float-left mt-24" | ||||
|             style={{ display: init ? "none" : "block" }} | ||||
|           > | ||||
|             <Form | ||||
|               form={form} | ||||
|               name="create-basic" | ||||
| @@ -203,13 +214,6 @@ export const MemberCreate: React.FC<PropInterface> = ({ | ||||
|                   placeholder="请选择学员所属部门" | ||||
|                 /> | ||||
|               </Form.Item> | ||||
|               <Form.Item label="身份证号" name="idCard"> | ||||
|                 <Input | ||||
|                   style={{ width: 274 }} | ||||
|                   allowClear | ||||
|                   placeholder="请填写学员身份证号" | ||||
|                 /> | ||||
|               </Form.Item> | ||||
|             </Form> | ||||
|           </div> | ||||
|         </Modal> | ||||
|   | ||||
| @@ -108,10 +108,10 @@ export const MemberUpdate: React.FC<PropInterface> = ({ | ||||
|     if (loading) { | ||||
|       return; | ||||
|     } | ||||
|     if (values.idCard !== "" && !ValidataCredentials(values.idCard)) { | ||||
|       message.error("请输入正确的身份证号!"); | ||||
|       return; | ||||
|     } | ||||
|     // if (values.idCard !== "" && !ValidataCredentials(values.idCard)) { | ||||
|     //   message.error("请输入正确的身份证号!"); | ||||
|     //   return; | ||||
|     // } | ||||
|     setLoading(true); | ||||
|     user | ||||
|       .updateUser( | ||||
| @@ -239,13 +239,6 @@ export const MemberUpdate: React.FC<PropInterface> = ({ | ||||
|                   placeholder="请选择学员所属部门" | ||||
|                 /> | ||||
|               </Form.Item> | ||||
|               <Form.Item label="身份证号" name="idCard"> | ||||
|                 <Input | ||||
|                   allowClear | ||||
|                   style={{ width: 274 }} | ||||
|                   placeholder="请填写学员身份证号" | ||||
|                 /> | ||||
|               </Form.Item> | ||||
|             </Form> | ||||
|           </div> | ||||
|         </Modal> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import React, { useState, useRef, useEffect } from "react"; | ||||
| import { Modal, Form, Input, Cascader, message } from "antd"; | ||||
| import { Modal, Form, Input, Cascader, message, Spin } from "antd"; | ||||
| import styles from "./create.module.less"; | ||||
| import { resourceCategory } from "../../../../api/index"; | ||||
|  | ||||
| @@ -19,11 +19,13 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({ | ||||
|   onCancel, | ||||
| }) => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const [init, setInit] = useState(true); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [categories, setCategories] = useState<any>([]); | ||||
|   const [parent_id, setParentId] = useState<number>(0); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setInit(true); | ||||
|     if (open) { | ||||
|       getParams(); | ||||
|     } | ||||
| @@ -54,6 +56,7 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({ | ||||
|         }); | ||||
|         setCategories(new_arr); | ||||
|       } | ||||
|       setInit(false); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -125,7 +128,15 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({ | ||||
|           onCancel={() => onCancel()} | ||||
|           okButtonProps={{ loading: loading }} | ||||
|         > | ||||
|           <div className="float-left mt-24"> | ||||
|           {init && ( | ||||
|             <div className="float-left text-center mt-30"> | ||||
|               <Spin></Spin> | ||||
|             </div> | ||||
|           )} | ||||
|           <div | ||||
|             className="float-left mt-24" | ||||
|             style={{ display: init ? "none" : "block" }} | ||||
|           > | ||||
|             <Form | ||||
|               form={form} | ||||
|               name="basic" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import React, { useState, useEffect } from "react"; | ||||
| import { Modal, Select, Switch, Form, Input, message } from "antd"; | ||||
| import { Modal, Select, Switch, Form, Input, message, Spin } from "antd"; | ||||
| import styles from "./create.module.less"; | ||||
| import { adminUser } from "../../../../api/index"; | ||||
|  | ||||
| @@ -22,10 +22,12 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({ | ||||
|   onCancel, | ||||
| }) => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const [init, setInit] = useState(true); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [roles, setRoles] = useState<selRoleModel[]>([]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setInit(true); | ||||
|     if (open) { | ||||
|       getParams(); | ||||
|     } | ||||
| @@ -56,6 +58,7 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({ | ||||
|         }); | ||||
|       } | ||||
|       setRoles(arr); | ||||
|       setInit(false); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -110,7 +113,15 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({ | ||||
|           maskClosable={false} | ||||
|           okButtonProps={{ loading: loading }} | ||||
|         > | ||||
|           <div className="float-left mt-24"> | ||||
|           {init && ( | ||||
|             <div className="float-left text-center mt-30"> | ||||
|               <Spin></Spin> | ||||
|             </div> | ||||
|           )} | ||||
|           <div | ||||
|             className="float-left mt-24" | ||||
|             style={{ display: init ? "none" : "block" }} | ||||
|           > | ||||
|             <Form | ||||
|               form={form} | ||||
|               name="basic" | ||||
|   | ||||
| @@ -107,10 +107,10 @@ const SystemLogPage = () => { | ||||
|       render: (_, record: any) => <span>{record.title}</span>, | ||||
|     }, | ||||
|     { | ||||
|       title: "IP地区", | ||||
|       title: "IP", | ||||
|       width: 250, | ||||
|       dataIndex: "ip_area", | ||||
|       render: (ip_area: string) => <span>{ip_area}</span>, | ||||
|       dataIndex: "ip", | ||||
|       render: (ip: string) => <span>{ip}</span>, | ||||
|     }, | ||||
|     { | ||||
|       title: "时间", | ||||
|   | ||||
| @@ -1,5 +1,14 @@ | ||||
| import React, { useState, useEffect } from "react"; | ||||
| import { Drawer, TreeSelect, Space, Button, Form, Input, message } from "antd"; | ||||
| import { | ||||
|   Drawer, | ||||
|   TreeSelect, | ||||
|   Space, | ||||
|   Button, | ||||
|   Form, | ||||
|   Input, | ||||
|   message, | ||||
|   Spin, | ||||
| } from "antd"; | ||||
| import styles from "./create.module.less"; | ||||
| import { adminRole } from "../../../../api/index"; | ||||
|  | ||||
| @@ -19,11 +28,13 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({ | ||||
|   onCancel, | ||||
| }) => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const [init, setInit] = useState(true); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [permissions, setPermissions] = useState<Option[]>([]); | ||||
|   const [actions, setActions] = useState<Option[]>([]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setInit(true); | ||||
|     if (open) { | ||||
|       getParams(); | ||||
|     } | ||||
| @@ -89,6 +100,7 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({ | ||||
|       } | ||||
|       setPermissions(arr); | ||||
|       setActions(arr2); | ||||
|       setInit(false); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -148,7 +160,15 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({ | ||||
|           } | ||||
|           width={634} | ||||
|         > | ||||
|           <div className="float-left mt-24"> | ||||
|           {init && ( | ||||
|             <div className="float-left text-center mt-30"> | ||||
|               <Spin></Spin> | ||||
|             </div> | ||||
|           )} | ||||
|           <div | ||||
|             className="float-left mt-24" | ||||
|             style={{ display: init ? "none" : "block" }} | ||||
|           > | ||||
|             <Form | ||||
|               form={form} | ||||
|               name="adminroles-create" | ||||
|   | ||||
| @@ -302,9 +302,9 @@ const SystemConfigPage = () => { | ||||
|           {logo && ( | ||||
|             <Form.Item | ||||
|               style={{ marginBottom: 30 }} | ||||
|               label="网站Logo" | ||||
|               label="PC学员端Logo" | ||||
|               name="system.logo" | ||||
|               labelCol={{ style: { marginTop: 4, marginLeft: 54 } }} | ||||
|               labelCol={{ style: { marginTop: 4, marginLeft: 24 } }} | ||||
|             > | ||||
|               <div className="d-flex"> | ||||
|                 <Image preview={false} height={40} src={logo} /> | ||||
| @@ -326,7 +326,7 @@ const SystemConfigPage = () => { | ||||
|           {!logo && ( | ||||
|             <Form.Item | ||||
|               style={{ marginBottom: 30 }} | ||||
|               label="网站Logo" | ||||
|               label="PC学员端Logo" | ||||
|               name="system.logo" | ||||
|             > | ||||
|               <div className="d-flex"> | ||||
| @@ -354,38 +354,38 @@ const SystemConfigPage = () => { | ||||
|           </Form.Item> | ||||
|           <Form.Item | ||||
|             style={{ marginBottom: 30 }} | ||||
|             label="PC端访问地址" | ||||
|             label="PC学员端地址" | ||||
|             name="system.pc_url" | ||||
|           > | ||||
|             <Input style={{ width: 274 }} placeholder="请填写PC端访问地址" /> | ||||
|             <Input style={{ width: 274 }} placeholder="请填写PC学员端地址" /> | ||||
|           </Form.Item> | ||||
|           <Form.Item | ||||
|             style={{ marginBottom: 30 }} | ||||
|             label="H5端访问地址" | ||||
|             label="H5学员端地址" | ||||
|             name="system.h5_url" | ||||
|           > | ||||
|             <Input style={{ width: 274 }} placeholder="请填写H5端访问地址" /> | ||||
|             <Input style={{ width: 274 }} placeholder="请填写H5学员端地址" /> | ||||
|           </Form.Item> | ||||
|           <Form.Item | ||||
|             style={{ marginBottom: 30 }} | ||||
|             label="网站标题" | ||||
|             label="学员端标题" | ||||
|             name="system.name" | ||||
|           > | ||||
|             <Input | ||||
|               style={{ width: 274 }} | ||||
|               allowClear | ||||
|               placeholder="请填写网站标题" | ||||
|               placeholder="请填写学员端标题" | ||||
|             /> | ||||
|           </Form.Item> | ||||
|           <Form.Item | ||||
|             style={{ marginBottom: 30 }} | ||||
|             label="网站页脚" | ||||
|             label="学员端页脚" | ||||
|             name="system.pc_index_footer_msg" | ||||
|           > | ||||
|             <Input | ||||
|               style={{ width: 274 }} | ||||
|               allowClear | ||||
|               placeholder="请填写网站页脚" | ||||
|               placeholder="请填写学员端页脚" | ||||
|             /> | ||||
|           </Form.Item> | ||||
|           <Form.Item | ||||
| @@ -467,13 +467,6 @@ const SystemConfigPage = () => { | ||||
|               > | ||||
|                 邮箱 | ||||
|               </Checkbox> | ||||
|               <Checkbox | ||||
|                 checked={idCardchecked} | ||||
|                 className="ml-24" | ||||
|                 onChange={addIdCard} | ||||
|               > | ||||
|                 身份证号 | ||||
|               </Checkbox> | ||||
|             </Space> | ||||
|           </Form.Item> | ||||
|           <Form.Item | ||||
|   | ||||
| @@ -1,15 +0,0 @@ | ||||
| import { ReportHandler } from 'web-vitals'; | ||||
|  | ||||
| const reportWebVitals = (onPerfEntry?: ReportHandler) => { | ||||
|   if (onPerfEntry && onPerfEntry instanceof Function) { | ||||
|     import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { | ||||
|       getCLS(onPerfEntry); | ||||
|       getFID(onPerfEntry); | ||||
|       getFCP(onPerfEntry); | ||||
|       getLCP(onPerfEntry); | ||||
|       getTTFB(onPerfEntry); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export default reportWebVitals; | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { createSlice } from "@reduxjs/toolkit"; | ||||
| import { message } from "antd"; | ||||
|  | ||||
| type UserInterface = { | ||||
|   id: number; | ||||
| @@ -10,12 +11,16 @@ type UserStoreInterface = { | ||||
|   user: UserInterface | null; | ||||
|   isLogin: boolean; | ||||
|   permissions: string[]; | ||||
|   uploadStatus: boolean; | ||||
|   uploadCateIds: number[]; | ||||
| }; | ||||
|  | ||||
| let defaultValue: UserStoreInterface = { | ||||
|   user: null, | ||||
|   isLogin: false, | ||||
|   permissions: [], | ||||
|   uploadStatus: false, | ||||
|   uploadCateIds: [], | ||||
| }; | ||||
|  | ||||
| const loginUserSlice = createSlice({ | ||||
| @@ -33,10 +38,21 @@ const loginUserSlice = createSlice({ | ||||
|       stage.value.user = null; | ||||
|       stage.value.isLogin = false; | ||||
|     }, | ||||
|     uploadAction(stage, e) { | ||||
|       if ( | ||||
|         stage.value.uploadStatus === true && | ||||
|         e.payload.uploadStatus === true | ||||
|       ) { | ||||
|         message.error("请点击右下角悬浮窗"); | ||||
|       } | ||||
|       stage.value.uploadStatus = e.payload.uploadStatus; | ||||
|       stage.value.uploadCateIds = e.payload.uploadCateIds; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| export default loginUserSlice.reducer; | ||||
| export const { loginAction, logoutAction } = loginUserSlice.actions; | ||||
| export const { loginAction, logoutAction, uploadAction } = | ||||
|   loginUserSlice.actions; | ||||
|  | ||||
| export type { UserStoreInterface }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user