diff --git a/src/api/course.ts b/src/api/course.ts index 53d62bf..70a4dec 100644 --- a/src/api/course.ts +++ b/src/api/course.ts @@ -31,3 +31,8 @@ export function playPing(courseId: number, hourId: number) { export function latestLearn() { return client.get(`/api/v1/user/latest-learn`, {}); } + +//下载课件 +export function downloadAttachment(courseId: number, id: number) { + return client.get(`/api/v1/course/${courseId}/attach/${id}/download`, {}); +} diff --git a/src/api/internal/httpClient.ts b/src/api/internal/httpClient.ts index 8225eeb..0dcec87 100644 --- a/src/api/internal/httpClient.ts +++ b/src/api/internal/httpClient.ts @@ -56,6 +56,7 @@ export class HttpClient { GoLogin(); } else if (status === 404) { // 跳转到404页面 + GoLogin(); } else if (status === 403) { // 跳转到无权限页面 } else if (status === 500) { diff --git a/src/components/bar-footer/index.tsx b/src/components/bar-footer/index.tsx index 794941a..2016d24 100644 --- a/src/components/bar-footer/index.tsx +++ b/src/components/bar-footer/index.tsx @@ -1,7 +1,7 @@ -import React, { useState } from "react"; -import { NavBar, Badge, TabBar } from "antd-mobile"; +import React from "react"; +import { TabBar } from "antd-mobile"; import styles from "./index.module.scss"; -import { Link, useNavigate, useLocation } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; export const TabBarFooter: React.FC = () => { const navigate = useNavigate(); diff --git a/src/main.scss b/src/main.scss index 6b1fc60..6f3a8fa 100644 --- a/src/main.scss +++ b/src/main.scss @@ -155,3 +155,15 @@ code { .dplayer-mobile-play { opacity: 1 !important; } + +.course-tab-box { + width: 100%; + min-height: 24px; + display: flex; + align-items: center; + position: relative; + margin-bottom: 20px; + .adm-tabs-tab-wrapper { + padding: 0 36px 0 0; + } +} diff --git a/src/pages/course/index.module.scss b/src/pages/course/index.module.scss index 99408f9..c6cd58e 100644 --- a/src/pages/course/index.module.scss +++ b/src/pages/course/index.module.scss @@ -79,6 +79,14 @@ padding: 20px; margin-top: -20px; z-index: 10; + .tabs { + width: 100%; + min-height: 24px; + display: flex; + align-items: center; + position: relative; + margin-bottom: 20px; + } .desc { width: 100%; height: auto; @@ -130,3 +138,65 @@ } } } + +.attachments-cont { + width: 100%; + height: auto; + display: flex; + flex-direction: column; + .attachments-item { + width: 100%; + height: 69px; + background: #fafafa; + border-radius: 7px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + box-sizing: border-box; + padding: 10px 15px 10px 10px; + text-align: left; + margin-bottom: 10px; + &:last-child { + margin-bottom: 0px; + } + .left-cont { + width: calc(100% - 34px); + display: flex; + flex-direction: column; + .label { + width: 100%; + height: 16px; + display: flex; + align-items: center; + span { + height: 16px; + font-size: 12px; + font-weight: 400; + color: rgba(0, 0, 0, 0.45); + line-height: 16px; + } + } + .title { + width: 100%; + margin-top: 14px; + height: 16px; + font-size: 14px; + font-weight: 400; + color: rgba(0, 0, 0, 0.88); + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + .download { + width: 34px; + font-size: 12px; + font-weight: 400; + color: #ff4d4f; + line-height: 16px; + text-align: right; + } + } +} diff --git a/src/pages/course/index.tsx b/src/pages/course/index.tsx index ea7ff95..23dfe08 100644 --- a/src/pages/course/index.tsx +++ b/src/pages/course/index.tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; -import { Image, ProgressCircle } from "antd-mobile"; +import { Image, ProgressCircle, Tabs, Toast } from "antd-mobile"; import styles from "./index.module.scss"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useLocation, useParams } from "react-router-dom"; import backIcon from "../../assets/images/commen/icon-back-n.png"; import { course as vod } from "../../api/index"; -import { isEmptyObject } from "../../utils/index"; +import { isEmptyObject, isWechat, isIOS } from "../../utils/index"; import { Empty } from "../../components"; import { HourCompenent } from "./compenents/hour"; @@ -16,10 +16,25 @@ type LocalCourseHour = { [key: number]: CourseHourModel[]; }; +type tabModal = { + key: number; + label: string; +}; + +type attachModal = { + id: number; + course_id: number; + rid: number; + sort: number; + title: string; + type: string; + url?: string; +}; + const CoursePage = () => { const { courseId } = useParams(); const navigate = useNavigate(); - + const result = new URLSearchParams(useLocation().search); const [course, setCourse] = useState(null); const [chapters, setChapters] = useState([]); const [hours, setHours] = useState(null); @@ -31,30 +46,43 @@ const CoursePage = () => { const [courseTypeText, setCourseTypeText] = useState(""); const [userCourseProgress, setUserCourseProgress] = useState(0); + const [tabKey, setTabKey] = useState(Number(result.get("tab") || 1)); + const [attachments, setAttachments] = useState([]); + const [items, setItems] = useState([]); + const [downLoadTemplateURL, setDownLoadTemplateURL] = useState(""); useEffect(() => { - if (courseId) { - getDetail(Number(courseId)); - } + getDetail(); }, [courseId]); - const getDetail = (cid: number) => { - vod.detail(cid).then((res: any) => { + const getDetail = () => { + vod.detail(Number(courseId)).then((res: any) => { let courseItem: CourseModel = res.data.course; - document.title = courseItem.title || "课程详情"; - setCourse(courseItem); setChapters(res.data.chapters); setHours(res.data.hours); - if (res.data.learn_record) { setLearnRecord(res.data.learn_record); } - if (res.data.learn_hour_records) { setLearnHourRecord(res.data.learn_hour_records); } + let arr = res.data.attachments; + let tabs = [ + { + key: 1, + label: `课程目录`, + }, + ]; + if (arr.length > 0) { + tabs.push({ + key: 2, + label: `课程附件`, + }); + setAttachments(arr); + } + setItems(tabs); }); }; @@ -78,13 +106,54 @@ const CoursePage = () => { navigate(`/course/${cid}/hour/${id}`); }; + const downLoadFile = (cid: number, id: number) => { + vod.downloadAttachment(cid, id).then((res: any) => { + if (isWechat()) { + if (isIOS()) { + Toast.show("请点击右上角···浏览器打开下载"); + } + var input = document.createElement("input"); + input.value = res.data.download_url; + document.body.appendChild(input); + input.select(); + document.execCommand("Copy"); + document.body.removeChild(input); + window.open(res.data.download_url); + } else { + if (isIOS()) { + setDownLoadTemplateURL(res.data.download_url); + setTimeout(() => { + let $do: any = document.querySelector("#downLoadExcel"); + $do.click(); + }, 500); + } else { + window.open(res.data.download_url); + } + } + }); + }; + return (
+
navigate(-1)} + onClick={() => { + if (window.history.length <= 1) { + // 将页面跳转到首页 + navigate("/"); + } else { + // 返回到前一个页面 + navigate(-1); + } + }} />
@@ -116,47 +185,51 @@ const CoursePage = () => {
- {course?.short_desc && ( +
+ { + setTabKey(Number(key)); + }} + style={{ + "--fixed-active-line-width": "20px", + "--active-line-height": "3px", + "--active-title-color": "rgba(0,0,0,0.88)", + "--active-line-border-radius": "2px", + "--title-font-size": "16px", + "--content-padding": "18px", + }} + > + {items.map((item) => ( + + ))} + +
+ {tabKey === 1 && ( <> -
{course.short_desc}
-
- - )} -
- {chapters.length === 0 && !hours && } + {course?.short_desc && ( + <> +
{course.short_desc}
+
+ + )} +
+ {chapters.length === 0 && !hours && } - {chapters.length === 0 && hours && ( -
- {hours[0].map((item: CourseHourModel) => ( -
- { - playVideo(cid, id); - }} - > -
- ))} -
- )} - - {chapters.length > 0 && hours && ( -
- {chapters.map((item: ChapterModel) => ( -
-
{item.name}
- {hours[item.id]?.map((it: CourseHourModel) => ( -
+ {chapters.length === 0 && hours && ( +
+ {hours[0].map((item: CourseHourModel) => ( +
{ playVideo(cid, id); }} @@ -164,10 +237,64 @@ const CoursePage = () => {
))}
- ))} + )} + + {chapters.length > 0 && hours && ( +
+ {chapters.map((item: ChapterModel) => ( +
+
{item.name}
+ {hours[item.id]?.map((it: CourseHourModel) => ( +
+ { + playVideo(cid, id); + }} + > +
+ ))} +
+ ))} +
+ )}
- )} -
+ + )} + {tabKey === 2 && ( +
+ {attachments.map((item: any, index: number) => ( +
+
+
+ + 课件 +
+
+ {item.title}.{item.ext} +
+
+
downLoadFile(item.course_id, item.id)} + > + 下载 +
+
+ ))} +
+ )}
); diff --git a/src/pages/index/index.module.scss b/src/pages/index/index.module.scss index 7e2a8ef..8ab7721 100644 --- a/src/pages/index/index.module.scss +++ b/src/pages/index/index.module.scss @@ -90,15 +90,7 @@ overflow: hidden; text-overflow: ellipsis; } -} - -.active-child-item { - width: 100%; - float: left; - height: auto; - box-sizing: border-box; - padding-left: 20px; - .category-child-tit { + .act-category-child-tit { width: 100%; font-size: 14px; font-weight: 400; diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index d7ff20f..83d66c9 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -5,7 +5,7 @@ import { user } from "../../api/index"; import styles from "./index.module.scss"; import { useSelector } from "react-redux"; import { useNavigate, useLocation } from "react-router-dom"; -import { Footer, TabBarFooter, Empty } from "../../components"; +import { Footer, Empty } from "../../components"; import { CoursesModel } from "./compenents/courses-model"; import { isEmptyObject } from "../../utils/index"; @@ -163,16 +163,13 @@ const IndexPage = () => { return ( <> {data.map((item: any) => ( -
+
{ setCategoryId(item.key); setCategoryText(item.title); @@ -324,7 +321,6 @@ const IndexPage = () => { )}
-
); }; diff --git a/src/pages/layouts/with-footer/index.tsx b/src/pages/layouts/with-footer/index.tsx new file mode 100644 index 0000000..cd6d412 --- /dev/null +++ b/src/pages/layouts/with-footer/index.tsx @@ -0,0 +1,17 @@ +import { Suspense } from "react"; +import { Outlet } from "react-router-dom"; +import LoadingPage from "../../loading"; +import { TabBarFooter } from "../../../components"; + +const WithoutHeaderWithoutFooter = () => { + return ( + <> + }> + + + + + ); +}; + +export default WithoutHeaderWithoutFooter; diff --git a/src/pages/layouts/without-footer/index.tsx b/src/pages/layouts/without-footer/index.tsx new file mode 100644 index 0000000..8791cea --- /dev/null +++ b/src/pages/layouts/without-footer/index.tsx @@ -0,0 +1,13 @@ +import { Suspense } from "react"; +import { Outlet } from "react-router-dom"; +import LoadingPage from "../../loading"; + +const WithoutHeaderWithoutFooter = () => { + return ( + }> + + + ); +}; + +export default WithoutHeaderWithoutFooter; diff --git a/src/pages/member/index.module.scss b/src/pages/member/index.module.scss index 9985024..8d3aa6c 100644 --- a/src/pages/member/index.module.scss +++ b/src/pages/member/index.module.scss @@ -1,5 +1,5 @@ .main-body { - position: fixed; + position: absolute; bottom: 0; left: 0; right: 0; @@ -160,7 +160,12 @@ bottom: 0px; left: 20px; right: 20px; - height: 253px; + min-height: 253px; + // box-sizing: border-box; + // padding-bottom: calc( + // 53px + constant(safe-area-inset-bottom) + // ); /* 兼容iOS 11.0 - 11.2 */ + // padding-bottom: calc(53px + env(safe-area-inset-bottom)); /* 兼容iOS 11.2+ */ .dialog-box { width: 100%; height: 162px; diff --git a/src/pages/member/index.tsx b/src/pages/member/index.tsx index be64460..7f5271d 100644 --- a/src/pages/member/index.tsx +++ b/src/pages/member/index.tsx @@ -7,7 +7,6 @@ import { loginAction, logoutAction } from "../../store/user/loginUserSlice"; import { ImageUploadItem } from "antd-mobile/es/components/image-uploader"; import styles from "./index.module.scss"; import { useDispatch, useSelector } from "react-redux"; -import { TabBarFooter } from "../../components"; import moreIcon from "../../assets/images/commen/icon-more.png"; const MemberPage = () => { @@ -317,7 +316,6 @@ const MemberPage = () => {
-
); }; diff --git a/src/pages/study/index.tsx b/src/pages/study/index.tsx index f327744..1b87bb1 100644 --- a/src/pages/study/index.tsx +++ b/src/pages/study/index.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { Skeleton } from "antd-mobile"; import styles from "./index.module.scss"; import { course } from "../../api/index"; -import { TabBarFooter, Empty } from "../../components"; +import { Empty } from "../../components"; import { CoursesModel } from "./compenents/courses-model"; import moment from "moment"; @@ -143,7 +143,6 @@ const StudyPage = () => { )} - ); }; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 656bcda..0deebb2 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,17 +1,29 @@ import { lazy } from "react"; import { RouteObject } from "react-router-dom"; import { system, user } from "../api"; - import { getToken } from "../utils"; +// 页面加载 import { InitPage } from "../pages/init"; -import IndexPage from "../pages/index/index"; import LoginPage from "../pages/login"; -import MemberPage from "../pages/member/index"; -import ChangePasswordPage from "../pages/change-password/index"; -import ChangeDepartmentPage from "../pages/change-department/index"; -import StudyPage from "../pages/study/index"; -import CoursePage from "../pages/course/index"; -import CoursePlayPage from "../pages/course/video"; +import WithFooter from "../pages/layouts/with-footer"; +import WithoutFooter from "../pages/layouts/without-footer"; + +//用户中心页面 +const MemberPage = lazy(() => import("../pages/member/index")); +//主页 +const IndexPage = lazy(() => import("../pages/index/index")); +//修改密码页面 +const ChangePasswordPage = lazy(() => import("../pages/change-password/index")); +//修改部门页面 +const ChangeDepartmentPage = lazy( + () => import("../pages/change-department/index") +); +//学习页面 +const StudyPage = lazy(() => import("../pages/study/index")); +//课程页面 +const CoursePage = lazy(() => import("../pages/course/index")); +const CoursePlayPage = lazy(() => import("../pages/course/video")); + import PrivateRoute from "../components/private-route"; let RootPage: any = null; @@ -53,35 +65,47 @@ const routes: RouteObject[] = [ children: [ { path: "/", - element: } />, + element: , + children: [ + { + path: "/", + element: } />, + }, + { + path: "/member", + element: } />, + }, + { + path: "/study", + element: } />, + }, + ], }, { - path: "/login", - element: , - }, - { - path: "/member", - element: } />, - }, - { - path: "/change-password", - element: } />, - }, - { - path: "/study", - element: } />, - }, - { - path: "/change-department", - element: } />, - }, - { - path: "/course/:courseId", - element: } />, - }, - { - path: "/course/:courseId/hour/:hourId", - element: } />, + path: "/", + element: , + children: [ + { + path: "/login", + element: , + }, + { + path: "/change-password", + element: } />, + }, + { + path: "/change-department", + element: } />, + }, + { + path: "/course/:courseId", + element: } />, + }, + { + path: "/course/:courseId/hour/:hourId", + element: } />, + }, + ], }, ], }, diff --git a/src/utils/index.ts b/src/utils/index.ts index 4b06c5e..fa64b91 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -80,3 +80,13 @@ export function isMobile() { export function isEmptyObject(obj: Object) { return Object.keys(obj).length === 0; } + +export function isWechat() { + let ua = window.navigator.userAgent.toLowerCase(); + return /micromessenger/.test(ua); +} + +export function isIOS() { + var u = navigator.userAgent; + return !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); +}