longyifan hai 5 meses
pai
achega
8900fc2b26

+ 3 - 3
src/layouts/SideMenu.tsx

@@ -226,12 +226,12 @@ function SideMenu  ({
                           ...courseInfo,
                           periodName: item.periodName,
                           courseTitle: item.courseTitle,
-                          subjectName: item.subjectName,
+                          subjectName: item.gradeSubjectName,
                           courseDate: `${item.courseDate.split(' ')[0]}(${item.lessonStartTime} ~ ${item.lessonEndTime})`
                         })
                         localStorage.setItem('periodName', item.periodName);
                         localStorage.setItem('courseTitle', item.courseTitle);
-                        localStorage.setItem('subjectName', item.subjectName);
+                        localStorage.setItem('subjectName', item.gradeSubjectName);
                         localStorage.setItem('courseDate', `${item.courseDate.split(' ')[0]}(${item.lessonStartTime} ~ ${item.lessonEndTime})`);
                       }}
                       className={item.id === activeCourseKey ? 'course-active' : ''}
@@ -258,7 +258,7 @@ function SideMenu  ({
                       <div className="course-info">
                         <div style={{ fontWeight: 'bold', fontSize: '18px' }}>{item.courseTitle}</div>
                         <div className="info-flex">
-                          <div>学科:{item.subjectName}</div>
+                          <div>学科:{item.gradeSubjectName}</div>
                           <div>班级:{`${item.periodName}${item.className}`}</div>
                         </div>
                         <div style={{ display: 'flex', justifyContent:'space-between', color: '#999999', fontSize: '12px' }}>

+ 44 - 32
src/pages/Login/index.tsx

@@ -6,11 +6,16 @@ import './styles/index.less'
 
 
 const LoginPage: React.FC = () => {
-  const redirect_url = window.location.host+"/login/callback";
+  const redirect_url = window.location.origin+"/login/callback";
   
   const navigate = useNavigate();
   const location = useLocation();
   useEffect(()=> {
+    
+  })
+  const [showLoader, setShowLoader] = useState<boolean>(true);
+
+  const thirdLogin = ()=> {
     const queryParams = new URLSearchParams(location.search);
     const sessionId = queryParams.get('sessionId');
    
@@ -21,42 +26,49 @@ const LoginPage: React.FC = () => {
       // 设置3秒后自动跳转(实际项目中替换为真实的认证逻辑)
       const timer = setTimeout(() => {
         window.location.href = "https://szkj.zjer.cn/hlwxx/webAuthorize?redirect_url="+redirect_url;
-      }, 3000);
+      }, 500);
       return () => clearTimeout(timer);
       
     }
-  })
-  const [showLoader, setShowLoader] = useState<boolean>(true);
+  }
   
   return (
-    <div className="unified-login-container">
-      {showLoader && (
-        <div className="brand-loader">
-          <div className="loader-logo">
-            <img src={Logo} alt="课堂实录-专家评审" />
-          </div>
-          <Spin 
-            size="large" 
-          />
-          <div className="security-badge">
-            <span>正在安全跳转到统一登录平台...</span>
-          </div>
-        </div>
-      )}
-    </div>
-    // <Flex style={{ height: '100vh' }} justify="center" align="center" gap="middle" vertical>
-    //   <h1>统一登录(没有测试账号,待提供)</h1>
-    //   <Button
-    //     style={{ width: "200px" }}
-    //     color="primary"
-    //     variant="solid"
-    //     onClick={() => {
-    //       navigate('/login/callback?sessionId=xxxxxxxx')
-    //     }}
-    //   >
-    //     登录
-    //   </Button>
-    // </Flex>
+    // <div className="unified-login-container">
+    //   {showLoader && (
+    //     <div className="brand-loader">
+    //       <div className="loader-logo">
+    //         <img src={Logo} alt="课堂实录-专家评审" />
+    //       </div>
+    //       <Spin 
+    //         size="large" 
+    //       />
+    //       <div className="security-badge">
+    //         <span>正在安全跳转到统一登录平台...</span>
+    //       </div>
+    //     </div>
+    //   )}
+    // </div>
+    <Flex style={{ height: '100vh' }} justify="center" align="center" gap="middle" vertical>
+      <h1>统一登录(没有测试账号,待提供) <br /> 现在暂时提供两种模式,方便测试和调试三方登录</h1>
+      <Button
+        style={{ width: "200px" }}
+        color="primary"
+        variant="solid"
+        onClick={() => {
+          navigate('/login/callback?sessionId=xxxxxxxx')
+        }}
+      >
+        测试登录
+      </Button>
+      <Button
+        style={{ width: "200px" }}
+        color="primary"
+        variant="solid"
+        onClick={thirdLogin}
+      >
+        统一登录
+      </Button>
+    </Flex>
   );
 }
 

+ 130 - 104
src/pages/Play/components/SyncPlayer.tsx

@@ -7,6 +7,7 @@ interface VideoSource {
   src: string;
   label: string;
   tagColor: string;
+  pos: string;
 }
 
 interface VideoState {
@@ -33,37 +34,68 @@ const SyncVideoPlayer: React.FC<SyncVideoPlayerProps> = ({
   const rightVideoRef = useRef<HTMLVideoElement>(null);
   const audioRef = useRef<HTMLAudioElement>(null);
   const [videos, setVideos] = useState<{
-    top: VideoSource;
-    left: VideoSource;
-    right: VideoSource;
+    '1': VideoSource;
+    '2': VideoSource;
+    '3': VideoSource;
   }>({
-    top: { src: topVideoSrc, label: '教室全景', tagColor: 'magenta' },
-    left: { src: leftVideoSrc, label: '教师特写', tagColor: 'blue' },
-    right: { src: rightVideoSrc, label: '课件板书', tagColor: 'green' }
+    '1': { src: topVideoSrc, label: '教师特写', tagColor: 'magenta', pos: 'top' },
+    '2': { src: leftVideoSrc, label: '课件板书', tagColor: 'blue', pos: 'left' },
+    '3': { src: rightVideoSrc, label: '教室全景', tagColor: 'green', pos: 'right' }
   });
 
   useEffect(()=> {
    
     setVideos({
       ...videos,
-      top: { src: topVideoSrc, label: '教室全景', tagColor: 'magenta' },
-      left: { src: leftVideoSrc, label: '教师特写', tagColor: 'blue' },
-      right: { src: rightVideoSrc, label: '课件板书', tagColor: 'green' }
+      '1': { src: topVideoSrc, label: '教师特写', tagColor: 'magenta' , pos: 'top'},
+      '2': { src: leftVideoSrc, label: '课件板书', tagColor: 'blue' , pos: 'left'},
+      '3': { src: rightVideoSrc, label: '教室全景', tagColor: 'green', pos: 'right' }
     })
   }, [topVideoSrc, leftVideoSrc, rightVideoSrc])
 
-  useEffect(()=> {
-    leftVideoRef.current?.load();
-    rightVideoRef.current?.load();
-    topVideoRef.current?.load();
-  }, [videos])
-
-  const handleSwitch = (position: 'left' | 'right') => {
-    setVideos((prev: VideoState) => ({
-      top: prev[position],
-      left: position === 'left' ? prev.top : prev.left,
-      right: position === 'right' ? prev.top : prev.right
-    }));
+  // useEffect(()=> {
+  //   console.log('load???');
+    
+  //   leftVideoRef.current?.load();
+  //   rightVideoRef.current?.load();
+  //   topVideoRef.current?.load();
+  // }, [videos])
+
+  const handleSwitch = (vKey: any, currentPos: any) => {
+
+    Object.keys(videos).forEach(v=> {
+      if(v === '1') {
+        const vItem =  videos[1];
+        if(vItem.pos === 'top') {
+          videos[1].pos = currentPos;
+        }
+      } else if(v === '2') {
+        const vItem =  videos[2];
+        if(vItem.pos === 'top') {
+          videos[2].pos = currentPos;
+        }
+      } else if(v === '3') {
+        const vItem =  videos[3];
+        if(vItem.pos === 'top') {
+          videos[3].pos = currentPos;
+        }
+      }
+    })
+
+    if(vKey === '1') {
+      videos[1].pos = 'top'
+    }
+    if(vKey === '2') {
+      videos[2].pos = 'top'
+    }
+    if(vKey === '3') {
+      videos[3].pos = 'top'
+    }
+    console.log(videos,'???');
+    
+    setVideos({
+      ...videos
+    })
   };
 
   const [isPlaying, setIsPlaying] = useState(false);
@@ -402,95 +434,89 @@ const SyncVideoPlayer: React.FC<SyncVideoPlayerProps> = ({
 
   return (
     <div className={`video-container ${isFullscreen ? 'fullscreen' : ''}`}>
-      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
-        <div className="video-wrapper">
-          <video
-            ref={topVideoRef}
+      <div className={`sync-play-wrapper${isFullscreen?'-full':''}`} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
+            {
+              Object.keys(videos).map((vKey: string)=> {
+                const vItem = vKey === '1' ? videos[1] : (vKey === '2'? videos[2]:videos[3]);
+                const vRef = vKey === '1' ? topVideoRef : (vKey === '2'? leftVideoRef: rightVideoRef);
+                return (
+                  <div key={`v-pos-${vKey}`} className={`video-wrapper sync-play${isFullscreen?'-full':''}-${vItem.pos}`}>
+                    <video
+                      preload="auto"
+                      ref={vRef}
+                      style={{ objectFit: 'contain', backgroundColor: '#000', width: '100%', height: '100%'}}
+                      muted={vKey !== '1'}
+                      controls={false}
+                      disablePictureInPicture
+                    >
+                      <source src={vItem.src} type="video/mp4" />
+                    </video>
+                    <div className="video-tag">
+                      <Tag color={vItem.tagColor}>
+                        {vItem.label}
+                      </Tag>
+                      { (vItem.pos === 'left' || vItem.pos === 'right') && (
+                        <Tag
+                          color="orange"
+                          style={{ cursor: 'pointer' }}
+                          onClick={() => handleSwitch(vKey, vItem.pos)}
+                        >
+                          主屏显示
+                        </Tag>
+                      )}
+                    </div>
+                  </div>
+                )
+              })
+            }
             
-            onEnded={togglePlay}
-            style={{ objectFit: 'contain',backgroundColor: '#000', width:'100%', height: 'calc((100vh - (88px + 64px + 52px))/3 * 2)' }}
-            controls={false}
-            preload="auto"
-            disablePictureInPicture
-          >
-            <source src={videos.top.src} type="video/mp4" />
-          </video>
-          <Tag className="video-tag top-tag" color={videos.top.tagColor}>{videos.top.label}</Tag>
-        </div>
-        <div style={{ display: 'flex', justifyContent: 'center', gap: 10, width: '100%' }}>
-          {['left', 'right'].map((pos, index) => (
-            <div key={pos} className="video-wrapper">
-              <video
-                preload="auto"
-                ref={pos === 'left' ? leftVideoRef : rightVideoRef}
-                // width={isFullscreen ? '700' : '600'}
-                style={{ objectFit: 'contain', backgroundColor: '#000',  width: '100%',height: 'calc((100vh - (88px + 64px + 52px))/3)' }}
-                muted
-                controls={false}
-                disablePictureInPicture
-              >
-                <source src={videos[pos].src} type="video/mp4" />
-              </video>
-              <div className="video-tag">
-                <Tag color={videos[pos].tagColor}>
-                  {videos[pos].label}
-                </Tag>
-                { !isPlaying && (
-                  <Tag
-                    color="orange"
-                    style={{ cursor: 'pointer' }}
-                    onClick={() => handleSwitch(pos)}
-                  >
-                    主屏显示
-                  </Tag>
-                )}
-              </div>
-            </div>
-          ))}
-        </div>
+        {/* <div style={{ display: 'flex', justifyContent: 'center', gap: 10, width: '100%' }}>
+          
+        </div> */}
+         <div className="video-controls sync-play-control">
+            {
+              waiting ? (
+                <div className="play-button-loader"></div>
+              ) : (
+                <Button onClick={togglePlay}>
+                  {
+                    isPlaying ? '❚❚' : '▶' 
+                  }
+                </Button>
+              )
+            }
+            <input
+              type="range"
+              min="0"
+              max={Math.floor(duration) || 100}
+              value={currentTime}
+              onChange={handleSeek}
+              onMouseDown={handleSeekMouseDown}
+              onMouseUp={handleSeekMouseUp}
+              className="progress"
+            />
+            <span className="time-display">
+            {formatTime(currentTime)} / {formatTime(duration)}
+            </span>
+            <input
+              type="range"
+              min="0"
+              max="1"
+              step="0.01"
+              value={volume}
+              onChange={handleVolumeChange}
+              className="volume"
+            />
+            <Button onClick={toggleFullscreen}>
+              {isFullscreen ? '⤢' : '⤡'}
+            </Button>
+          </div>
       </div>
 
       {/* <audio ref={audioRef} src={audioSrc} /> */}
 
       {/* 自定义控制器 */}
-      <div className="video-controls">
-        {
-          waiting ? (
-            <div className="play-button-loader"></div>
-          ) : (
-            <Button onClick={togglePlay}>
-              {
-                isPlaying ? '❚❚' : '▶' 
-              }
-            </Button>
-          )
-        }
-        <input
-          type="range"
-          min="0"
-          max={Math.floor(duration) || 100}
-          value={currentTime}
-          onChange={handleSeek}
-          onMouseDown={handleSeekMouseDown}
-          onMouseUp={handleSeekMouseUp}
-          className="progress"
-        />
-        <span className="time-display">
-         {formatTime(currentTime)} / {formatTime(duration)}
-        </span>
-        <input
-          type="range"
-          min="0"
-          max="1"
-          step="0.01"
-          value={volume}
-          onChange={handleVolumeChange}
-          className="volume"
-        />
-        <Button onClick={toggleFullscreen}>
-          {isFullscreen ? '⤢' : '⤡'}
-        </Button>
-      </div>
+     
     </div>
   );
 };

+ 70 - 0
src/pages/Play/components/style/index.less

@@ -95,4 +95,74 @@
 
 @keyframes spin {
   to { transform: rotate(360deg); }
+}
+
+
+.sync-play-wrapper {
+  position: relative;
+  height: calc((100vh - (88px + 64px)));
+}
+
+.sync-play-wrapper-full {
+  position: relative;
+  height: calc(100vh);
+}
+.sync-play-top {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: calc((100vh + 1px - (88px + 64px + 52px))/3 * 2);
+  border-bottom: 1px solid #F00;
+}
+
+.sync-play-left {
+  position: absolute;
+  top: calc((100vh + 1px - (88px + 64px + 52px))/3 * 2);
+  left: 0;
+  width: calc(50%);
+  height: calc((100vh - (88px + 64px + 52px))/3);
+  border-right: 1px solid #F00;
+}
+
+.sync-play-right {
+  position: absolute;
+  top: calc((100vh + 1px - (88px + 64px + 52px))/3 * 2);
+  left: calc(50%);
+  width: calc(50%);
+  height: calc((100vh - (88px + 64px + 52px))/3);
+}
+
+
+// full
+.sync-play-full-top {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: calc((100vh + 1px - 52px)/3 * 2);
+  border-bottom: 1px solid #F00;
+}
+
+.sync-play-full-left {
+  position: absolute;
+  top: calc((100vh + 1px - 52px)/3 * 2);
+  left: 0;
+  width: calc(50%);
+  height: calc((100vh - 52px)/3);
+  border-right: 1px solid #F00;
+}
+
+.sync-play-full-right {
+  position: absolute;
+  top: calc((100vh + 1px - 52px)/3 * 2);
+  left: calc(50%);
+  width: calc(50%);
+  height: calc((100vh - 52px)/3);
+}
+
+.sync-play-control {
+  position: absolute;
+  bottom: 0;
+  left: 0;
 }

+ 37 - 22
src/pages/Record/index.tsx

@@ -5,11 +5,13 @@ import React, { useEffect, useRef, useState } from 'react';
 import ProList from '@ant-design/pro-list';
 import { Image, Tabs } from 'antd';
 import './style/index.less';
+import dayjs from 'dayjs';
 import { history } from "@umijs/max";
 import { useLocation, useModel, useRouteProps } from '@@/exports';
 import { getExpertActives, getSelectSubject, getTeacherCourses } from '@/services/record/RecordController';
 import { ActionType } from '@ant-design/pro-table/lib';
 import ErrorImg from '@/assets/img/error_assets.svg';
+import { TableFormItem } from '@ant-design/pro-table/es/components/Form/FormRender';
 
 const TableList: React.FC<unknown> = () => {
   const routeProps = useRouteProps();
@@ -21,6 +23,8 @@ const TableList: React.FC<unknown> = () => {
   const [gradeSubjects, setGradeSubjects] = useState<{ label: string, value: string }[]>([]);
   const [courseStart, setCourseStart] = useState<string>();
   const [courseEnd, setCourseEnd] = useState<string>();
+  const [defaultParams, setDefaultParams] = useState({});
+  const searchFormRef = useRef<any>(null);
   const { setCourseInfo } = useModel("global")
   useEffect(() => {
     if(location.pathname) {
@@ -43,10 +47,12 @@ const TableList: React.FC<unknown> = () => {
       const activeData = await getExpertActives()
       if (activeData.code === 200) {
         if (activeData.data.length > 0) {
-          setActives(activeData.data.map((item: any) =>
-            ({ label: item.activeName, value: item.id })
+          setActives(activeData.data.map((item: any, index: number) =>
+            ({ label: item.activeName, value: item.id, checked: index === 0 })
           ));
+          await searchFormRef.current?.setFieldsValue({activeId: activeData.data[0].id})
         }
+        searchFormRef.current?.submit();
       }
 
       // 获取学科下拉列表
@@ -64,11 +70,32 @@ const TableList: React.FC<unknown> = () => {
     oneData();
   }, []);
 
+  const requestData = async (params: any) => {
+    delete params.teachTime;
+    const { pageSize } = params
+    const data = await getTeacherCourses({
+      ...params,
+      teacherId: teacherId,
+      size: pageSize,
+      courseStart: courseStart ? dayjs(courseStart).format('YYYY-MM-DD 00:00:00'):null,
+      courseEnd: courseEnd ? dayjs(courseEnd).format('YYYY-MM-DD 23:59:59') : null,
+    })
+    return {
+      data: data.data.records,
+      total: data.data.total,
+    };
+  }
+
   return (
     <PageContainer style={{height: 'calc(100vh - 20px)'}} className="page-box">
       <ProList<any>
         search={{}}
+        manualRequest
+        onReset={()=> {
+          
+        }}
         actionRef={actionRef}
+        formRef={searchFormRef}
         className="table-box"
         pagination={{
           defaultPageSize: 20,
@@ -78,24 +105,24 @@ const TableList: React.FC<unknown> = () => {
         onItem={(record: any) => {
           return {
             onClick: () => {
-              console.log(record);
             },
           };
         }}
         renderItem={(item) => (
           <div
+            className='course-item'
             style={{ backgroundColor: '#F8FAFC', cursor: 'pointer',borderRadius: '15px 15px 0 0' }}
             onClick={() => {
               history.push('/play?teacherId=' + teacherId + '&courseId=' + item.id);
               setCourseInfo({
                 periodName: item.periodName,
                 courseTitle: item.courseTitle,
-                subjectName: item.subjectName,
+                subjectName: item.gradeSubjectName,
                 courseDate: `${item.courseDate.split(' ')[0]}(${item.lessonStartTime} ~ ${item.lessonEndTime})`
               })
               localStorage.setItem('periodName', item.periodName);
               localStorage.setItem('courseTitle', item.courseTitle);
-              localStorage.setItem('subjectName', item.subjectName);
+              localStorage.setItem('subjectName', item.gradeSubjectName);
               localStorage.setItem('courseDate', `${item.courseDate.split(' ')[0]}(${item.lessonStartTime} ~ ${item.lessonEndTime})`);
             }}
           >
@@ -143,6 +170,7 @@ const TableList: React.FC<unknown> = () => {
             fieldProps: {
               placeholder: '请输入评审活动',
               options: actives,
+              allowClear: false
             }
           },
           gradeSubjectName: {
@@ -151,7 +179,7 @@ const TableList: React.FC<unknown> = () => {
             valueType: 'select',
             fieldProps: {
               placeholder: '请选择授课学科',
-              options: gradeSubjects,
+              options: gradeSubjects
             }
           },
           teacherTime: {
@@ -159,7 +187,8 @@ const TableList: React.FC<unknown> = () => {
             dataIndex: 'teachTime',
             valueType: 'dateRange',
             fieldProps: {
-              format: 'YYYY-MM-DD HH:mm:ss',
+              defaultValue: [undefined, undefined],
+              format: 'YYYY-MM-DD',
               placeholder: '请选择授课时间',
               onChange: (value: [Date, Date]) => {
                 if (value) {
@@ -174,21 +203,7 @@ const TableList: React.FC<unknown> = () => {
           },
           actions: {},
         }}
-        request={async (params) => {
-          delete params.teachTime;
-          const { pageSize } = params
-          const data = await getTeacherCourses({
-            ...params,
-            teacherId: teacherId,
-            size: pageSize,
-            courseStart: courseStart,
-            courseEnd: courseEnd,
-          })
-          return {
-            data: data.data.records,
-            total: data.data.total,
-          };
-        }}
+        request={requestData}
       />
     </PageContainer>
   );

+ 37 - 1
src/pages/Record/style/index.less

@@ -6,4 +6,40 @@
     justify-content: space-between;
     margin-bottom: 5px;
   }
-}
+}
+ .table-box .ant-pro-card-body .ant-list-items {
+    gap: 12px !important;
+  }
+
+/* 中等屏幕(平板/小笔记本) */
+@media (min-width: 700px) and (max-width: 1000px) { 
+  /* 样式覆盖 */ 
+  .course-item {
+    flex-basis: calc(100% - 6px) !important;
+  }
+}
+
+/* 中等屏幕(平板/小笔记本) */
+@media (min-width: 1000px) and (max-width: 1300px) { 
+  /* 样式覆盖 */ 
+  .course-item {
+    flex-basis: calc(50% - 6px) !important;
+  }
+}
+
+/* 大屏幕(桌面显示器) */
+@media (min-width: 1300px) and (max-width: 1600px) { 
+  /* 样式覆盖 */ 
+  .course-item {
+    flex-basis: calc(33.33% - 9px) !important;
+  }
+}
+
+/* 超大屏幕(大显示器) */
+@media (min-width: 1600px) { 
+  /* 样式覆盖 */ 
+  .course-item {
+    flex-basis: calc(25% - 10px) !important;
+  }
+}
+