18 Commits
v1.3 ... main

Author SHA1 Message Date
xxx
96da44a884 移除 react-ga 依赖 2024-02-20 14:59:07 +08:00
xxx
39250531fb fixed: pnpm install 和 build 报错 2024-02-20 14:51:33 +08:00
xxx
490ea214b3 移除xgplayer 2024-02-20 14:29:44 +08:00
unknown
b0d0063fc1 重部署系统时部门storage存储优化以及error页面显示优化 2024-02-01 09:50:00 +08:00
unknown
8614d4ebaf 底部组件优化 2024-01-31 18:34:10 +08:00
unknown
46bb144f0a 退出登录优化 2024-01-26 10:53:44 +08:00
xxx
6e920d8ea1 移除hls.min.js 2024-01-08 16:14:11 +08:00
unknown
8154ee3130 登录封面替换 2024-01-08 12:02:31 +08:00
xxx
e056775fc0 Merge branch 'fix/1116' into dev 2023-11-16 14:57:44 +08:00
unknown
f5b4a70741 首页学习时间不显示天数 2023-11-16 14:57:38 +08:00
unknown
e1bca31883 课程详情tab优化 2023-11-16 14:55:26 +08:00
白书科技
f1985d0b8f !6 优化
Merge pull request !6 from 白书科技/fix/1007
2023-10-07 09:55:43 +00:00
unknown
3eafab1d41 首页学习时间不显示天数 2023-10-07 17:53:57 +08:00
白书科技
66fd8486db !5 优化
Merge pull request !5 from 白书科技/fix/0922
2023-09-24 00:47:27 +00:00
unknown
30612785b5 检测视频重复播放 2023-09-22 16:50:31 +08:00
unknown
1fbe67f228 检测视频重复播放 2023-09-22 16:46:43 +08:00
unknown
7a64180d04 iconfont优化 2023-09-22 15:28:35 +08:00
xxx
eaee82649e 兼容安卓钉钉 2023-09-21 10:32:21 +08:00
21 changed files with 127 additions and 104 deletions

View File

@@ -1,2 +1 @@
VITE_APP_URL= VITE_APP_URL=
VITE_G_ID=

2
.gitignore vendored
View File

@@ -33,3 +33,5 @@ yarn.lock
deploy-test.sh deploy-test.sh
deploy-prod.sh deploy-prod.sh
deploy-demo.sh deploy-demo.sh
pnpm-lock.yaml

View File

@@ -13,10 +13,5 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <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> </body>
</html> </html>

View File

@@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "playedu-pc-interface",
"private": true, "private": false,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -9,28 +9,31 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.0",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.3",
"add": "^2.0.6", "add": "^2.0.6",
"antd": "^5.3.2", "antd": "^5.3.2",
"axios": "^1.3.4", "axios": "^1.3.4",
"dayjs": "^1.11.10",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"match-sorter": "^6.3.1", "match-sorter": "^6.3.1",
"moment": "^2.29.4", "moment": "^2.29.4",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-ga": "^3.3.1",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.9.0", "react-router-dom": "^6.9.0",
"redux": "^4.2.1", "redux": "^4.2.1",
"sort-by": "^1.2.0" "sort-by": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.2.0",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.2.0",
"@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-react-swc": "^3.0.0", "@vitejs/plugin-react-swc": "^3.0.0",
"rollup-plugin-gzip": "^3.1.0", "rollup-plugin-gzip": "^3.1.0",
"sass": "^1.59.3", "sass": "^1.59.3",
"terser": "^5.20.0",
"typescript": "^4.9.3", "typescript": "^4.9.3",
"vite": "^4.2.0" "vite": "^4.2.0"
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +1,13 @@
import { Suspense, useEffect } from "react"; import { Suspense } from "react";
import ReactGA from "react-ga"; import { useRoutes } from "react-router-dom";
import { useLocation, useRoutes } from "react-router-dom";
import routes from "./routes"; import routes from "./routes";
import "./App.scss"; import "./App.scss";
import LoadingPage from "./pages/loading"; import LoadingPage from "./pages/loading";
const G_ID = import.meta.env.VITE_G_ID || "";
if (G_ID) {
ReactGA.initialize(G_ID);
}
const App = () => { const App = () => {
const views = useRoutes(routes); const views = useRoutes(routes);
const location = useLocation(); return <Suspense fallback={<LoadingPage />}>{views}</Suspense>;
useEffect(() => {
if (!G_ID) {
return;
}
ReactGA.pageview(location.pathname + location.search);
}, [location]);
return (
<Suspense fallback={<LoadingPage />}>
{views}
</Suspense>
);
}; };
export default App; export default App;

View File

@@ -1,14 +1,21 @@
import axios, { Axios, AxiosResponse } from "axios"; import axios, { Axios, AxiosResponse } from "axios";
import { message } from "antd"; import { message } from "antd";
import { getToken, clearToken } from "../../utils/index"; import {
getToken,
clearToken,
clearDepKey,
clearDepName,
} from "../../utils/index";
const GoLogin = () => { const GoLogin = () => {
clearToken(); clearToken();
clearDepName();
clearDepKey();
window.location.href = "/login"; window.location.href = "/login";
}; };
const GoError = () => { const GoError = (status: number) => {
window.location.href = "/error"; window.location.href = "/error?status=" + status;
}; };
export class HttpClient { export class HttpClient {
@@ -56,13 +63,13 @@ export class HttpClient {
GoLogin(); GoLogin();
} else if (status === 404) { } else if (status === 404) {
// 跳转到404页面 // 跳转到404页面
GoError(); GoError(404);
} else if (status === 403) { } else if (status === 403) {
// 跳转到无权限页面 // 跳转到无权限页面
GoError(); GoError(403);
} else if (status === 500) { } else if (status === 500) {
// 跳转到500异常页面 // 跳转到500异常页面
GoError(); GoError(500);
} }
return Promise.reject(error.response); return Promise.reject(error.response);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -1,5 +1,3 @@
@import "./assets/iconfont/iconfont.css";
$primaryColor: #ff4d4f; $primaryColor: #ff4d4f;
:root { :root {
@@ -35,6 +33,18 @@ body {
min-height: 100vh; min-height: 100vh;
} }
.layout-box {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.footer-box {
flex: 1;
display: flex;
flex-direction: column-reverse;
}
.main-body { .main-body {
width: 100%; width: 100%;
height: auto; height: auto;

View File

@@ -4,6 +4,7 @@ import store from "./store";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { ConfigProvider } from "antd"; import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN"; import zhCN from "antd/locale/zh_CN";
import "./assets/iconfont/iconfont.css";
import App from "./App"; import App from "./App";
import "./index.scss"; //全局样式 import "./index.scss"; //全局样式
import AutoScorllTop from "./AutoTop"; import AutoScorllTop from "./AutoTop";

View File

@@ -101,7 +101,7 @@
align-items: center; align-items: center;
position: relative; position: relative;
.tab-item { .tab-item {
width: 64px; min-width: 64px;
height: 48px; height: 48px;
margin-right: 50px; margin-right: 50px;
transition: all 0.2s; transition: all 0.2s;
@@ -114,7 +114,7 @@
} }
} }
.tit { .tit {
width: 64px; width: 100%;
height: 40px; height: 40px;
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
@@ -123,7 +123,7 @@
} }
} }
.tab-active-item { .tab-active-item {
width: 64px; min-width: 64px;
height: 48px; height: 48px;
cursor: pointer; cursor: pointer;
margin-right: 50px; margin-right: 50px;
@@ -133,7 +133,7 @@
opacity: 0.8; opacity: 0.8;
} }
.tit { .tit {
width: 64px; width: 100%;
height: 40px; height: 40px;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;

View File

@@ -64,6 +64,7 @@
margin: 0 auto; margin: 0 auto;
border-radius: 8px; border-radius: 8px;
position: relative; position: relative;
.alert-message { .alert-message {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -80,6 +81,12 @@
font-size: 18px; font-size: 18px;
color: white; color: white;
z-index: 100; z-index: 100;
.des-video {
font-size: 15px;
font-weight: 400;
color: #ffffff;
line-height: 15px;
}
.alert-button { .alert-button {
width: 200px; width: 200px;
height: 54px; height: 54px;

View File

@@ -5,8 +5,10 @@ import { useSelector } from "react-redux";
import { course as Course } from "../../api/index"; import { course as Course } from "../../api/index";
import { ArrowLeftOutlined } from "@ant-design/icons"; import { ArrowLeftOutlined } from "@ant-design/icons";
import { message } from "antd"; import { message } from "antd";
import { getPlayId, savePlayId } from "../../utils";
declare const window: any; declare const window: any;
var timer: any = null;
const CoursePalyPage = () => { const CoursePalyPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -28,8 +30,10 @@ const CoursePalyPage = () => {
const playRef = useRef(0); const playRef = useRef(0);
const watchRef = useRef(0); const watchRef = useRef(0);
const totalRef = useRef(0); const totalRef = useRef(0);
const [checkPlayerStatus, setCheckPlayerStatus] = useState(false);
useEffect(() => { useEffect(() => {
timer && clearInterval(timer);
getCourse(); getCourse();
getDetail(); getDetail();
document.oncontextmenu = function (e) { document.oncontextmenu = function (e) {
@@ -37,7 +41,9 @@ const CoursePalyPage = () => {
e = e || window.event; e = e || window.event;
return false; return false;
}; };
return () => { return () => {
timer && clearInterval(timer);
document.oncontextmenu = function (e) { document.oncontextmenu = function (e) {
/*恢复浏览器默认右键事件*/ /*恢复浏览器默认右键事件*/
e = e || window.event; e = e || window.event;
@@ -123,6 +129,7 @@ const CoursePalyPage = () => {
(res: any) => { (res: any) => {
setPlayUrl(res.data.url); setPlayUrl(res.data.url);
initDPlayer(res.data.url, 0, data); initDPlayer(res.data.url, 0, data);
savePlayId(String(params.courseId) + "-" + String(params.hourId));
} }
); );
}; };
@@ -185,6 +192,7 @@ const CoursePalyPage = () => {
window.player && window.player.destroy(); window.player && window.player.destroy();
}); });
setLoading(false); setLoading(false);
checkPlayer();
}; };
const playTimeUpdate = (duration: number, isEnd: boolean) => { const playTimeUpdate = (duration: number, isEnd: boolean) => {
@@ -201,6 +209,22 @@ const CoursePalyPage = () => {
} }
}; };
const checkPlayer = () => {
timer = setInterval(() => {
let playId = getPlayId();
if (
playId &&
playId !== String(params.courseId) + "-" + String(params.hourId)
) {
timer && clearInterval(timer);
window.player && window.player.destroy();
setCheckPlayerStatus(true);
} else {
setCheckPlayerStatus(false);
}
}, 5000);
};
const goNextVideo = () => { const goNextVideo = () => {
const index = totalHours.findIndex( const index = totalHours.findIndex(
(i: any) => i.id === Number(params.hourId) (i: any) => i.id === Number(params.hourId)
@@ -234,6 +258,7 @@ const CoursePalyPage = () => {
<div <div
className={styles["close-btn"]} className={styles["close-btn"]}
onClick={() => { onClick={() => {
timer && clearInterval(timer);
window.player && window.player.destroy(); window.player && window.player.destroy();
document.oncontextmenu = function (e) { document.oncontextmenu = function (e) {
/*恢复浏览器默认右键事件*/ /*恢复浏览器默认右键事件*/
@@ -256,6 +281,13 @@ const CoursePalyPage = () => {
id="meedu-player-container" id="meedu-player-container"
style={{ borderRadius: 8 }} style={{ borderRadius: 8 }}
></div> ></div>
{checkPlayerStatus && (
<div className={styles["alert-message"]}>
<div className={styles["des-video"]}>
</div>
</div>
)}
{playendedStatus && ( {playendedStatus && (
<div className={styles["alert-message"]}> <div className={styles["alert-message"]}>
{isLastpage && ( {isLastpage && (

View File

@@ -1,14 +1,17 @@
import { useState } from "react";
import { Button, Result } from "antd"; import { Button, Result } from "antd";
import { useNavigate } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
const ErrorPage = () => { const ErrorPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const result = new URLSearchParams(useLocation().search);
const [status, setStatus] = useState(String(result.get("status") || "404"));
return ( return (
<Result <Result
status="404" status="404"
title="404" title={status}
subTitle="您访问的页面不存在" subTitle="您访问的页面不存在"
className={styles["main"]} className={styles["main"]}
extra={ extra={

View File

@@ -298,27 +298,17 @@ const IndexPage = () => {
{studyTimeFormat(stats.today_learn_duration)[0] || {studyTimeFormat(stats.today_learn_duration)[0] ||
0}{" "} 0}{" "}
</strong> </strong>
</>
)}
{studyTimeFormat(stats.today_learn_duration)[1] !== 0 && (
<>
<strong>
{" "}
{studyTimeFormat(stats.today_learn_duration)[1] ||
0}{" "}
</strong>
</> </>
)} )}
<strong> <strong>
{" "} {" "}
{studyTimeFormat(stats.today_learn_duration)[2] || 0}{" "} {studyTimeFormat(stats.today_learn_duration)[1] || 0}{" "}
</strong> </strong>
<strong> <strong>
{" "} {" "}
{studyTimeFormat(stats.today_learn_duration)[3] || 0}{" "} {studyTimeFormat(stats.today_learn_duration)[2] || 0}{" "}
</strong> </strong>
</div> </div>
@@ -331,27 +321,17 @@ const IndexPage = () => {
{studyTimeFormat(stats.learn_duration || 0)[0] || {studyTimeFormat(stats.learn_duration || 0)[0] ||
0}{" "} 0}{" "}
</strong> </strong>
</>
)}
{studyTimeFormat(stats.learn_duration || 0)[1] !== 0 && (
<>
<strong>
{" "}
{studyTimeFormat(stats.learn_duration || 0)[1] ||
0}{" "}
</strong>
</> </>
)} )}
<strong> <strong>
{" "} {" "}
{studyTimeFormat(stats.learn_duration || 0)[2] || 0}{" "} {studyTimeFormat(stats.learn_duration || 0)[1] || 0}{" "}
</strong> </strong>
<strong> <strong>
{" "} {" "}
{studyTimeFormat(stats.learn_duration || 0)[3] || 0}{" "} {studyTimeFormat(stats.learn_duration || 0)[2] || 0}{" "}
</strong> </strong>
</div> </div>

View File

@@ -5,13 +5,15 @@ import LoadingPage from "../../loading";
const WithHeaderWithFooter = () => { const WithHeaderWithFooter = () => {
return ( return (
<> <div className="layout-box">
<Header></Header> <Header></Header>
<Suspense fallback={<LoadingPage height="100vh" />}> <Suspense fallback={<LoadingPage height="100vh" />}>
<Outlet /> <Outlet />
</Suspense> </Suspense>
<Footer></Footer> <div className="footer-box">
</> <Footer></Footer>
</div>
</div>
); );
}; };

View File

@@ -5,12 +5,14 @@ import LoadingPage from "../../loading";
const WithoutHeaderWithFooter = () => { const WithoutHeaderWithFooter = () => {
return ( return (
<> <div className="layout-box">
<Suspense fallback={<LoadingPage height="100vh" />}> <Suspense fallback={<LoadingPage height="100vh" />}>
<Outlet /> <Outlet />
</Suspense> </Suspense>
<Footer></Footer> <div className="footer-box">
</> <Footer></Footer>
</div>
</div>
); );
}; };

View File

@@ -31,7 +31,7 @@ const loginUserSlice = createSlice({
stage.value.user = e.payload.user; stage.value.user = e.payload.user;
stage.value.departments = e.payload.departments; stage.value.departments = e.payload.departments;
stage.value.isLogin = true; stage.value.isLogin = true;
if (e.payload.departments.length > 0 && stage.value.currentDepId === 0) { if (e.payload.departments.length > 0 && !getDepKey()) {
stage.value.currentDepId = e.payload.departments[0].id; stage.value.currentDepId = e.payload.departments[0].id;
setDepName(e.payload.departments[0].name); setDepName(e.payload.departments[0].name);
} }

View File

@@ -28,7 +28,6 @@ export function durationFormat(dateStr: number) {
export function studyTimeFormat(dateStr: number) { export function studyTimeFormat(dateStr: number) {
var d = moment.duration(dateStr / 1000, "seconds"); var d = moment.duration(dateStr / 1000, "seconds");
let value = []; let value = [];
value.push(Math.floor(d.asDays()));
value.push(d.hours()); value.push(d.hours());
value.push(d.minutes()); value.push(d.minutes());
value.push(d.seconds()); value.push(d.seconds());
@@ -111,3 +110,15 @@ export function isMobile() {
); );
return flag; return flag;
} }
export function getPlayId(): string {
return window.localStorage.getItem("playedu-play-id") || "";
}
export function savePlayId(id: string) {
window.localStorage.setItem("playedu-play-id", id);
}
export function clearPlayId() {
window.localStorage.removeItem("playedu-play-id");
}

View File

@@ -1,6 +1,7 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import gzipPlugin from "rollup-plugin-gzip"; import gzipPlugin from "rollup-plugin-gzip";
import legacy from "@vitejs/plugin-legacy";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@@ -8,7 +9,15 @@ export default defineConfig({
host: "0.0.0.0", host: "0.0.0.0",
port: 9797, port: 9797,
}, },
plugins: [react()], plugins: [
react(),
legacy({
targets: ["chrome 52"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
renderLegacyChunks: true,
modernPolyfills: true,
}),
],
build: { build: {
rollupOptions: { rollupOptions: {
plugins: [gzipPlugin()], plugins: [gzipPlugin()],