mirror of
https://github.com/PlayEdu/frontend.git
synced 2025-12-24 10:09:47 +08:00
Compare commits
13 Commits
v1.0-beta.
...
v1.0-beta.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8023ce2a69 | ||
|
|
230f17dfe5 | ||
|
|
a67db5dbfc | ||
|
|
7e16c748f5 | ||
|
|
76d3a939d2 | ||
|
|
a0754cde00 | ||
|
|
da56cd085e | ||
|
|
df2ab806c6 | ||
|
|
123cf31503 | ||
|
|
2a5eab39a6 | ||
|
|
ec32b7a6d1 | ||
|
|
2bb831bf7c | ||
|
|
4cb89d2020 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/node_modules
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
@@ -1 +1,2 @@
|
|||||||
VITE_APP_URL=
|
VITE_APP_URL=
|
||||||
|
VITE_G_ID=
|
||||||
12
Dockerfile
12
Dockerfile
@@ -1,5 +1,13 @@
|
|||||||
|
FROM node:lts-slim as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
RUN yarn config set registry https://registry.npm.taobao.org && yarn && yarn build
|
||||||
|
|
||||||
FROM nginx:1.23.4-alpine-slim
|
FROM nginx:1.23.4-alpine-slim
|
||||||
|
|
||||||
COPY dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
COPY --from=builder /app/docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
@@ -10,18 +10,20 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "^1.9.3",
|
"@reduxjs/toolkit": "^1.9.3",
|
||||||
|
"add": "^2.0.6",
|
||||||
"antd": "^5.3.2",
|
"antd": "^5.3.2",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"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",
|
||||||
"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"
|
||||||
"web-vitals": "^3.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
|
|||||||
36
src/App.tsx
36
src/App.tsx
@@ -1,31 +1,31 @@
|
|||||||
import { useRoutes } from "react-router-dom";
|
import { Suspense, useEffect } from "react";
|
||||||
|
import ReactGA from "react-ga";
|
||||||
|
import { useLocation, useRoutes } from "react-router-dom";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import "./App.scss";
|
import "./App.scss";
|
||||||
import { Suspense } from "react";
|
|
||||||
import LoadingPage from "./pages/loading";
|
import LoadingPage from "./pages/loading";
|
||||||
import { user } from "./api/index";
|
|
||||||
import { getToken } from "./utils/index";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { loginAction } from "./store/user/loginUserSlice";
|
|
||||||
|
|
||||||
function App() {
|
const G_ID = import.meta.env.VITE_G_ID || "";
|
||||||
|
if (G_ID) {
|
||||||
|
ReactGA.initialize(G_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
const Views = () => useRoutes(routes);
|
const Views = () => useRoutes(routes);
|
||||||
const dispatch = useDispatch();
|
|
||||||
const getUser = () => {
|
const location = useLocation();
|
||||||
user.detail().then((res: any) => {
|
useEffect(() => {
|
||||||
const data = res.data;
|
if (!G_ID) {
|
||||||
dispatch(loginAction(data));
|
return;
|
||||||
});
|
}
|
||||||
};
|
ReactGA.pageview(location.pathname + location.search);
|
||||||
if (getToken()) {
|
}, [location]);
|
||||||
getUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<LoadingPage />}>
|
<Suspense fallback={<LoadingPage />}>
|
||||||
<Views />
|
<Views />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
13
src/AutoTop.ts
Normal file
13
src/AutoTop.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useLayoutEffect } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
const AutoScorllTop: React.FC<{ children: any }> = ({ children }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
document.documentElement.scrollTo(0, 0);
|
||||||
|
}, [location.pathname]);
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AutoScorllTop;
|
||||||
@@ -8,6 +8,12 @@ import {
|
|||||||
logoutAction,
|
logoutAction,
|
||||||
saveCurrentDepId,
|
saveCurrentDepId,
|
||||||
} from "../../store/user/loginUserSlice";
|
} from "../../store/user/loginUserSlice";
|
||||||
|
import {
|
||||||
|
setDepKey,
|
||||||
|
setDepName,
|
||||||
|
getDepName,
|
||||||
|
clearToken,
|
||||||
|
} from "../../utils/index";
|
||||||
import { ChangePasswordModel } from "../change-password";
|
import { ChangePasswordModel } from "../change-password";
|
||||||
import { UserInfoModel } from "../user-info";
|
import { UserInfoModel } from "../user-info";
|
||||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
@@ -32,7 +38,7 @@ export const Header: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (departments.length > 0) {
|
if (departments.length > 0) {
|
||||||
setCurrentDepartment(departments[0].name);
|
setCurrentDepartment(getDepName() || departments[0].name);
|
||||||
const arr: any = [
|
const arr: any = [
|
||||||
{
|
{
|
||||||
key: "1",
|
key: "1",
|
||||||
@@ -67,6 +73,7 @@ export const Header: React.FC = () => {
|
|||||||
cancelText: "取消",
|
cancelText: "取消",
|
||||||
onOk() {
|
onOk() {
|
||||||
dispatch(logoutAction());
|
dispatch(logoutAction());
|
||||||
|
clearToken();
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
},
|
},
|
||||||
onCancel() {
|
onCancel() {
|
||||||
@@ -129,6 +136,8 @@ export const Header: React.FC = () => {
|
|||||||
onOk() {
|
onOk() {
|
||||||
setCurrentDepartment(name);
|
setCurrentDepartment(name);
|
||||||
dispatch(saveCurrentDepId(Number(key)));
|
dispatch(saveCurrentDepId(Number(key)));
|
||||||
|
setDepKey(key);
|
||||||
|
setDepName(name);
|
||||||
const box = [...departments];
|
const box = [...departments];
|
||||||
const arr: any = [
|
const arr: any = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export const UserInfoModel: React.FC<PropInterface> = ({ open, onCancel }) => {
|
|||||||
const [idCard, setIdCard] = useState<string>("");
|
const [idCard, setIdCard] = useState<string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUser();
|
if (open) {
|
||||||
|
getUser();
|
||||||
|
}
|
||||||
}, [form, open]);
|
}, [form, open]);
|
||||||
|
|
||||||
const getUser = () => {
|
const getUser = () => {
|
||||||
|
|||||||
12
src/main.tsx
12
src/main.tsx
@@ -1,13 +1,12 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import reportWebVitals from "./reportWebVitals";
|
|
||||||
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 App from "./App";
|
import App from "./App";
|
||||||
import "./index.scss"; //全局样式
|
import "./index.scss"; //全局样式
|
||||||
|
import AutoScorllTop from "./AutoTop";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@@ -16,13 +15,10 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|||||||
theme={{ token: { colorPrimary: "#ff4d4f" } }}
|
theme={{ token: { colorPrimary: "#ff4d4f" } }}
|
||||||
>
|
>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<AutoScorllTop>
|
||||||
|
<App />
|
||||||
|
</AutoScorllTop>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</Provider>
|
</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();
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import iconRoute from "../../assets/images/commen/icon-route.png";
|
|||||||
import { studyTimeFormat } from "../../utils/index";
|
import { studyTimeFormat } from "../../utils/index";
|
||||||
|
|
||||||
const IndexPage = () => {
|
const IndexPage = () => {
|
||||||
|
document.title = "首页";
|
||||||
const systemConfig = useSelector((state: any) => state.systemConfig.value);
|
const systemConfig = useSelector((state: any) => state.systemConfig.value);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [tabKey, setTabKey] = useState(0);
|
const [tabKey, setTabKey] = useState(0);
|
||||||
|
|||||||
@@ -1,17 +1,48 @@
|
|||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
// import styles from "./index.module.scss";
|
// import styles from "./index.module.scss";
|
||||||
import { saveConfigAction } from "../../store/system/systemConfigSlice";
|
import {
|
||||||
|
SystemConfigStoreInterface,
|
||||||
|
saveConfigAction,
|
||||||
|
} from "../../store/system/systemConfigSlice";
|
||||||
|
import { loginAction } from "../../store/user/loginUserSlice";
|
||||||
import { Header, NoHeader, Footer } from "../../compenents";
|
import { Header, NoHeader, Footer } from "../../compenents";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
config: Map<string, string>;
|
loginData?: any;
|
||||||
|
configData?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InitPage = (props: Props) => {
|
export const InitPage = (props: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
dispatch(saveConfigAction(props.config));
|
if (props.loginData) {
|
||||||
|
dispatch(loginAction(props.loginData));
|
||||||
|
}
|
||||||
|
if (props.configData) {
|
||||||
|
let config: SystemConfigStoreInterface = {
|
||||||
|
//系统配置
|
||||||
|
systemApiUrl: props.configData["system-api-url"],
|
||||||
|
systemH5Url: props.configData["system-h5-url"],
|
||||||
|
systemLogo: props.configData["system-logo"],
|
||||||
|
systemName: props.configData["system-name"],
|
||||||
|
systemPcUrl: props.configData["system-pc-url"],
|
||||||
|
pcIndexFooterMsg: props.configData["system-pc-index-footer-msg"],
|
||||||
|
//播放器配置
|
||||||
|
playerPoster: props.configData["player-poster"],
|
||||||
|
playerIsEnabledBulletSecret:
|
||||||
|
props.configData["player-is-enabled-bullet-secret"] &&
|
||||||
|
props.configData["player-is-enabled-bullet-secret"] === "1"
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
playerBulletSecretText: props.configData["player-bullet-secret-text"],
|
||||||
|
playerBulletSecretColor: props.configData["player-bullet-secret-color"],
|
||||||
|
playerBulletSecretOpacity:
|
||||||
|
props.configData["player-bullet-secret-opacity"],
|
||||||
|
};
|
||||||
|
dispatch(saveConfigAction(config));
|
||||||
|
}
|
||||||
|
|
||||||
const pathname = useLocation().pathname;
|
const pathname = useLocation().pathname;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const LatestLearnPage = () => {
|
const LatestLearnPage = () => {
|
||||||
|
document.title = "最近学习";
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const systemConfig = useSelector((state: any) => state.systemConfig.value);
|
const systemConfig = useSelector((state: any) => state.systemConfig.value);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|||||||
@@ -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,57 +1,38 @@
|
|||||||
import { lazy } from "react";
|
import { lazy } from "react";
|
||||||
import { RouteObject } from "react-router-dom";
|
import { RouteObject } from "react-router-dom";
|
||||||
import { system } from "../api";
|
import { system, user } from "../api";
|
||||||
import { SystemConfigStoreInterface } from "../store/system/systemConfigSlice";
|
import { SystemConfigStoreInterface } from "../store/system/systemConfigSlice";
|
||||||
|
|
||||||
|
import { getToken } from "../utils";
|
||||||
import { InitPage } from "../pages/init";
|
import { InitPage } from "../pages/init";
|
||||||
import CoursePage from "../pages/course";
|
import CoursePage from "../pages/course";
|
||||||
import IndexPage from "../pages/index";
|
import IndexPage from "../pages/index";
|
||||||
import LatestLearnPage from "../pages/latest-learn";
|
import LatestLearnPage from "../pages/latest-learn";
|
||||||
import LoginPage from "../pages/login";
|
import LoginPage from "../pages/login";
|
||||||
|
|
||||||
let config: SystemConfigStoreInterface = {
|
let RootPage: any = null;
|
||||||
systemApiUrl: "",
|
if (getToken()) {
|
||||||
systemPcUrl: "",
|
RootPage = lazy(async () => {
|
||||||
systemH5Url: "",
|
return new Promise<any>(async (resolve) => {
|
||||||
systemLogo: "",
|
try {
|
||||||
systemName: "",
|
let configRes: any = await system.config();
|
||||||
pcIndexFooterMsg: "",
|
let userRes: any = await user.detail();
|
||||||
playerPoster: "",
|
resolve({
|
||||||
playerIsEnabledBulletSecret: false,
|
default: (
|
||||||
playerBulletSecretText: "",
|
<InitPage configData={configRes.data} loginData={userRes.data} />
|
||||||
playerBulletSecretColor: "",
|
),
|
||||||
playerBulletSecretOpacity: "",
|
});
|
||||||
};
|
} catch (e) {
|
||||||
|
console.error("系统初始化失败", e);
|
||||||
const Init = lazy(async () => {
|
}
|
||||||
return new Promise<any>((resolve) => {
|
|
||||||
system.config().then((res: any) => {
|
|
||||||
//系统配置
|
|
||||||
config.systemApiUrl = res.data["system-api-url"];
|
|
||||||
config.systemH5Url = res.data["system-h5-url"];
|
|
||||||
config.systemLogo = res.data["system-logo"];
|
|
||||||
config.systemName = res.data["system-name"];
|
|
||||||
config.systemPcUrl = res.data["system-pc-url"];
|
|
||||||
config.pcIndexFooterMsg = res.data["system-pc-index-footer-msg"];
|
|
||||||
|
|
||||||
//播放器配置
|
|
||||||
config.playerPoster = res.data["player-poster"];
|
|
||||||
config.playerIsEnabledBulletSecret =
|
|
||||||
res.data["player-is-enabled-bullet-secret"] &&
|
|
||||||
res.data["player-is-enabled-bullet-secret"] === "1"
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
config.playerBulletSecretText = res.data["player-bullet-secret-text"];
|
|
||||||
config.playerBulletSecretColor = res.data["player-bullet-secret-color"];
|
|
||||||
config.playerBulletSecretOpacity =
|
|
||||||
res.data["player-bullet-secret-opacity"];
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
default: InitPage,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
if (window.location.pathname !== "/login") {
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
RootPage = <InitPage />;
|
||||||
|
}
|
||||||
|
|
||||||
// 懒加载
|
// 懒加载
|
||||||
// const LoginPage = lazy(() => import("../pages/login"));
|
// const LoginPage = lazy(() => import("../pages/login"));
|
||||||
@@ -62,7 +43,7 @@ const Init = lazy(async () => {
|
|||||||
const routes: RouteObject[] = [
|
const routes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <Init config={config} />,
|
element: RootPage,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { getDepKey } from "../../utils/index";
|
||||||
|
|
||||||
type UserStoreInterface = {
|
type UserStoreInterface = {
|
||||||
user: null;
|
user: null;
|
||||||
@@ -10,7 +11,7 @@ type UserStoreInterface = {
|
|||||||
let defaultValue: UserStoreInterface = {
|
let defaultValue: UserStoreInterface = {
|
||||||
user: null,
|
user: null,
|
||||||
departments: [],
|
departments: [],
|
||||||
currentDepId: 0,
|
currentDepId: Number(getDepKey()) || 0,
|
||||||
isLogin: false,
|
isLogin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -72,3 +72,26 @@ export function inStrArray(array: string[], value: string): boolean {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDepKey(): string {
|
||||||
|
return window.localStorage.getItem("playedu-frontend-depatmentKey") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDepKey(token: string) {
|
||||||
|
window.localStorage.setItem("playedu-frontend-depatmentKey", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearDepKey() {
|
||||||
|
window.localStorage.removeItem("playedu-frontend-depatmentKey");
|
||||||
|
}
|
||||||
|
export function getDepName(): string {
|
||||||
|
return window.localStorage.getItem("playedu-frontend-depatmentName") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDepName(token: string) {
|
||||||
|
window.localStorage.setItem("playedu-frontend-depatmentName", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearDepName() {
|
||||||
|
window.localStorage.removeItem("playedu-frontend-depatmentName");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user