mirror of
https://github.com/PlayEdu/PlayEdu
synced 2025-10-27 07:01:37 +08:00
!14 存储桶改为private
* 后台 使用许可页面 * 优化:移除API访问地址配置 * 后台、pc、h5 删除无用配置 * docker部署优化 * 2.0 networkMode=bridge * changelog * 学员端权限为空报错 * h5 我的页面请求优化 * 缓存查询 * 后台 学员列表报错、线上课-上架时间字段优化 * 后台、pc、h5 使用签名地址 * 学员端接口修改 * 后台、pc 使用签名地址 * 后台 使用签名地址 * 上传接口 * 上传接口 * 系统配置 * 线上课封面 * bucket由public改为private * 资源相关表实体及对象修改 * 统一数据库脚本
This commit is contained in:
3635
playedu-pc/pnpm-lock.yaml
generated
Normal file
3635
playedu-pc/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
playedu-pc/src/assets/thumb/avatar.png
Normal file
BIN
playedu-pc/src/assets/thumb/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
BIN
playedu-pc/src/assets/thumb/thumb1.png
Normal file
BIN
playedu-pc/src/assets/thumb/thumb1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
playedu-pc/src/assets/thumb/thumb2.png
Normal file
BIN
playedu-pc/src/assets/thumb/thumb2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
playedu-pc/src/assets/thumb/thumb3.png
Normal file
BIN
playedu-pc/src/assets/thumb/thumb3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -18,6 +18,7 @@ import { ChangePasswordModel } from "../change-password";
|
||||
import { UserInfoModel } from "../user-info";
|
||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||
import logo from "../../assets/logo.png";
|
||||
import memberDefaultAvatar from "../../assets/thumb/avatar.png";
|
||||
const { confirm } = Modal;
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
@@ -35,6 +36,9 @@ export const Header: React.FC = () => {
|
||||
const [departmentsMenu, setDepartmentsMenu] = useState<any>([]);
|
||||
const [currentDepartment, setCurrentDepartment] = useState<string>("");
|
||||
const [currentNav, serCurrentNav] = useState(location.pathname);
|
||||
const resourceUrl = useSelector(
|
||||
(state: any) => state.loginUser.value.resourceUrl
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (departments.length > 0) {
|
||||
@@ -178,7 +182,7 @@ export const Header: React.FC = () => {
|
||||
<div className="d-flex">
|
||||
<Link to="/" className={styles["App-logo"]}>
|
||||
{/* 此处为版权标识,严禁删改 */}
|
||||
<img src={config.systemLogo || logo} />
|
||||
<img src={config.resourceUrl[config.systemLogo] || logo} />
|
||||
</Link>
|
||||
<div className={styles["navs"]}>
|
||||
{navs.map((item: any) => (
|
||||
@@ -221,7 +225,11 @@ export const Header: React.FC = () => {
|
||||
<Image
|
||||
loading="lazy"
|
||||
style={{ width: 36, height: 36, borderRadius: "50%" }}
|
||||
src={user.avatar}
|
||||
src={
|
||||
user.avatar === -1
|
||||
? memberDefaultAvatar
|
||||
: resourceUrl[user.avatar]
|
||||
}
|
||||
preview={false}
|
||||
/>
|
||||
<span className="ml-8 c-admin">{user.name}</span>
|
||||
|
||||
@@ -13,7 +13,10 @@ export const NoHeader: React.FC = () => {
|
||||
return (
|
||||
<div className={styles["app-header"]}>
|
||||
<div className={styles["main-header"]}>
|
||||
<img src={config.systemLogo || logo} className={styles["App-logo"]} />
|
||||
<img
|
||||
src={config.resourceUrl[config.systemLogo] || logo}
|
||||
className={styles["App-logo"]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { loginAction } from "../../store/user/loginUserSlice";
|
||||
import type { UploadProps } from "antd";
|
||||
import config from "../../js/config";
|
||||
import { getToken, changeAppUrl } from "../../utils/index";
|
||||
import memberDefaultAvatar from "../../assets/thumb/avatar.png";
|
||||
|
||||
interface PropInterface {
|
||||
open: boolean;
|
||||
@@ -17,9 +18,10 @@ export const UserInfoModel: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [avatar, setAvatar] = useState<string>("");
|
||||
const [avatar, setAvatar] = useState(0);
|
||||
const [name, setName] = useState<string>("");
|
||||
const [idCard, setIdCard] = useState<string>("");
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -30,6 +32,7 @@ export const UserInfoModel: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
const getUser = () => {
|
||||
user.detail().then((res: any) => {
|
||||
setAvatar(res.data.user.avatar);
|
||||
setResourceUrl(res.data.resource_url);
|
||||
setName(res.data.user.name);
|
||||
setIdCard(res.data.user.id_card);
|
||||
dispatch(loginAction(res.data));
|
||||
@@ -106,7 +109,11 @@ export const UserInfoModel: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
width={60}
|
||||
height={60}
|
||||
style={{ borderRadius: "50%" }}
|
||||
src={avatar}
|
||||
src={
|
||||
avatar === -1
|
||||
? memberDefaultAvatar
|
||||
: resourceUrl[avatar]
|
||||
}
|
||||
preview={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,9 @@ import mediaIcon from "../../assets/images/commen/icon-medal.png";
|
||||
import { HourCompenent } from "./compenents/hour";
|
||||
import { Empty } from "../../compenents";
|
||||
import iconRoute from "../../assets/images/commen/icon-route.png";
|
||||
import defaultThumb1 from "../../assets/thumb/thumb1.png";
|
||||
import defaultThumb2 from "../../assets/thumb/thumb2.png";
|
||||
import defaultThumb3 from "../../assets/thumb/thumb3.png";
|
||||
|
||||
type TabModel = {
|
||||
key: number;
|
||||
@@ -56,6 +59,7 @@ const CoursePage = () => {
|
||||
);
|
||||
const [tabKey, setTabKey] = useState(Number(result.get("tab") || 1));
|
||||
const [attachments, setAttachments] = useState<AttachModel[]>([]);
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
const [items, setItems] = useState<TabModel[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -70,6 +74,7 @@ const CoursePage = () => {
|
||||
setCourse(res.data.course);
|
||||
setChapters(res.data.chapters);
|
||||
setHours(res.data.hours);
|
||||
setResourceUrl(res.data.resource_url);
|
||||
if (res.data.learn_record) {
|
||||
setLearnRecord(res.data.learn_record);
|
||||
}
|
||||
@@ -104,9 +109,35 @@ const CoursePage = () => {
|
||||
navigate("/course/" + params.courseId + "?tab=" + key);
|
||||
};
|
||||
|
||||
const downLoadFile = (cid: number, id: number) => {
|
||||
const downLoadFile = (
|
||||
cid: number,
|
||||
id: number,
|
||||
rid: number,
|
||||
fileName: string,
|
||||
type: string
|
||||
) => {
|
||||
Course.downloadAttachment(cid, id).then((res: any) => {
|
||||
window.open(res.data.download_url);
|
||||
if (type === "TXT") {
|
||||
fetch(res.data.resource_url[rid])
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const n_url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = n_url;
|
||||
a.download = fileName; // 设置下载的文件名
|
||||
document.body.appendChild(a);
|
||||
a.click(); // 触发点击事件
|
||||
// 释放 URL 对象
|
||||
URL.revokeObjectURL(n_url);
|
||||
document.body.removeChild(a);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("下载文件时出错:", error);
|
||||
});
|
||||
} else {
|
||||
window.open(res.data.resource_url[rid]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -131,13 +162,23 @@ const CoursePage = () => {
|
||||
<div className={styles["top-cont"]}>
|
||||
<div className="j-b-flex">
|
||||
<div className="d-flex">
|
||||
<Image
|
||||
width={120}
|
||||
height={90}
|
||||
style={{ borderRadius: 10 }}
|
||||
preview={false}
|
||||
src={course?.thumb}
|
||||
/>
|
||||
{course ? (
|
||||
<Image
|
||||
width={120}
|
||||
height={90}
|
||||
style={{ borderRadius: 10 }}
|
||||
preview={false}
|
||||
src={
|
||||
course.thumb === -1
|
||||
? defaultThumb1
|
||||
: course.thumb === -2
|
||||
? defaultThumb2
|
||||
: course.thumb === -3
|
||||
? defaultThumb3
|
||||
: resourceUrl[course.thumb]
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["title"]}>{course?.title}</div>
|
||||
<div className={styles["status"]}>
|
||||
@@ -327,7 +368,15 @@ const CoursePage = () => {
|
||||
</div>
|
||||
<div
|
||||
className={styles["download"]}
|
||||
onClick={() => downLoadFile(item.course_id, item.id)}
|
||||
onClick={() =>
|
||||
downLoadFile(
|
||||
item.course_id,
|
||||
item.id,
|
||||
item.rid,
|
||||
`${item.title}.${item.ext}`,
|
||||
item.type
|
||||
)
|
||||
}
|
||||
>
|
||||
下载
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ const CoursePalyPage = () => {
|
||||
const [totalHours, setTotalHours] = useState<HourModel[]>([]);
|
||||
const [playingTime, setPlayingTime] = useState(0);
|
||||
const [watchedSeconds, setWatchedSeconds] = useState(0);
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
const myRef = useRef(0);
|
||||
const playRef = useRef(0);
|
||||
const watchRef = useRef(0);
|
||||
@@ -105,7 +106,7 @@ const CoursePalyPage = () => {
|
||||
} else if (record && record.is_finished === 1) {
|
||||
setWatchedSeconds(res.data.hour.duration);
|
||||
}
|
||||
getVideoUrl(params);
|
||||
getVideoUrl(res.data.hour.rid, params);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -113,11 +114,12 @@ const CoursePalyPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getVideoUrl = (data: any) => {
|
||||
const getVideoUrl = (rid: number, data: any) => {
|
||||
Course.playUrl(Number(params.courseId), Number(params.hourId)).then(
|
||||
(res: any) => {
|
||||
setPlayUrl(res.data.url);
|
||||
initDPlayer(res.data.url, 0, data);
|
||||
setResourceUrl(res.data.resource_url[rid]);
|
||||
setPlayUrl(res.data.resource_url[rid]);
|
||||
initDPlayer(res.data.resource_url[rid], 0, data);
|
||||
savePlayId(String(params.courseId) + "-" + String(params.hourId));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -9,6 +9,9 @@ import { Empty } from "../../compenents";
|
||||
import myLesoon from "../../assets/images/commen/icon-mylesoon.png";
|
||||
import studyTime from "../../assets/images/commen/icon-studytime.png";
|
||||
import iconRoute from "../../assets/images/commen/icon-route.png";
|
||||
import defaultThumb1 from "../../assets/thumb/thumb1.png";
|
||||
import defaultThumb2 from "../../assets/thumb/thumb2.png";
|
||||
import defaultThumb3 from "../../assets/thumb/thumb3.png";
|
||||
import { studyTimeFormat } from "../../utils/index";
|
||||
|
||||
type StatsModel = {
|
||||
@@ -54,6 +57,7 @@ const IndexPage = () => {
|
||||
useState<LearnCourseRecordsModel>({});
|
||||
const [learnCourseHourCount, setLearnCourseHourCount] = useState<any>({});
|
||||
const [stats, setStats] = useState<StatsModel | null>(null);
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
const currentDepId = useSelector(
|
||||
(state: any) => state.loginUser.value.currentDepId
|
||||
);
|
||||
@@ -90,6 +94,7 @@ const IndexPage = () => {
|
||||
setStats(res.data.stats);
|
||||
setLearnCourseRecords(records);
|
||||
setLearnCourseHourCount(res.data.user_course_hour_count);
|
||||
setResourceUrl(res.data.resource_url);
|
||||
if (tabKey === 0) {
|
||||
setCoursesList(res.data.courses);
|
||||
} else if (tabKey === 1) {
|
||||
@@ -408,7 +413,15 @@ const IndexPage = () => {
|
||||
<CoursesModel
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
thumb={item.thumb}
|
||||
thumb={
|
||||
item.thumb === -1
|
||||
? defaultThumb1
|
||||
: item.thumb === -2
|
||||
? defaultThumb2
|
||||
: item.thumb === -3
|
||||
? defaultThumb3
|
||||
: resourceUrl[item.thumb]
|
||||
}
|
||||
isRequired={item.is_required}
|
||||
progress={Math.floor(
|
||||
learnCourseRecords[item.id].progress / 100
|
||||
@@ -422,7 +435,15 @@ const IndexPage = () => {
|
||||
<CoursesModel
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
thumb={item.thumb}
|
||||
thumb={
|
||||
item.thumb === -1
|
||||
? defaultThumb1
|
||||
: item.thumb === -2
|
||||
? defaultThumb2
|
||||
: item.thumb === -3
|
||||
? defaultThumb3
|
||||
: resourceUrl[item.thumb]
|
||||
}
|
||||
isRequired={item.is_required}
|
||||
progress={1}
|
||||
></CoursesModel>
|
||||
@@ -432,7 +453,15 @@ const IndexPage = () => {
|
||||
<CoursesModel
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
thumb={item.thumb}
|
||||
thumb={
|
||||
item.thumb === -1
|
||||
? defaultThumb1
|
||||
: item.thumb === -2
|
||||
? defaultThumb2
|
||||
: item.thumb === -3
|
||||
? defaultThumb3
|
||||
: resourceUrl[item.thumb]
|
||||
}
|
||||
isRequired={item.is_required}
|
||||
progress={0}
|
||||
></CoursesModel>
|
||||
|
||||
@@ -25,11 +25,11 @@ export const InitPage = (props: Props) => {
|
||||
let config: SystemConfigStoreInterface = {
|
||||
//系统配置
|
||||
"ldap-enabled": props.configData["ldap-enabled"],
|
||||
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"],
|
||||
resourceUrl: props.configData["resource_url"],
|
||||
pcIndexFooterMsg: props.configData["system-pc-index-footer-msg"],
|
||||
//播放器配置
|
||||
playerPoster: props.configData["player-poster"],
|
||||
|
||||
@@ -4,6 +4,9 @@ import { course } from "../../api/index";
|
||||
import { Row, Col, Spin, Image, Progress } from "antd";
|
||||
import { Empty } from "../../compenents";
|
||||
import mediaIcon from "../../assets/images/commen/icon-medal.png";
|
||||
import defaultThumb1 from "../../assets/thumb/thumb1.png";
|
||||
import defaultThumb2 from "../../assets/thumb/thumb2.png";
|
||||
import defaultThumb3 from "../../assets/thumb/thumb3.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
@@ -47,6 +50,7 @@ const LatestLearnPage = () => {
|
||||
const systemConfig = useSelector((state: any) => state.systemConfig.value);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [courses, setCourses] = useState<LastLearnModel[]>([]);
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
|
||||
useEffect(() => {
|
||||
getCourses();
|
||||
@@ -55,7 +59,10 @@ const LatestLearnPage = () => {
|
||||
const getCourses = () => {
|
||||
setLoading(true);
|
||||
course.latestLearn().then((res: any) => {
|
||||
setCourses(res.data);
|
||||
if (res.data.resource_url && res.data.user_latest_learns) {
|
||||
setResourceUrl(res.data.resource_url);
|
||||
setCourses(res.data.user_latest_learns);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
@@ -91,7 +98,15 @@ const LatestLearnPage = () => {
|
||||
<div style={{ width: 120 }}>
|
||||
<Image
|
||||
loading="lazy"
|
||||
src={item.course.thumb}
|
||||
src={
|
||||
item.course.thumb === -1
|
||||
? defaultThumb1
|
||||
: item.course.thumb === -2
|
||||
? defaultThumb2
|
||||
: item.course.thumb === -3
|
||||
? defaultThumb3
|
||||
: resourceUrl[item.course.thumb]
|
||||
}
|
||||
width={120}
|
||||
height={90}
|
||||
style={{ borderRadius: 10 }}
|
||||
|
||||
6
playedu-pc/src/playedu.d.ts
vendored
6
playedu-pc/src/playedu.d.ts
vendored
@@ -7,7 +7,7 @@ declare global {
|
||||
is_required: number;
|
||||
is_show: number;
|
||||
short_desc: string;
|
||||
thumb: string;
|
||||
thumb: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ declare global {
|
||||
updated_at: string;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
interface ResourceUrlModel {
|
||||
[key: number]: string;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
type SystemConfigStoreInterface = {
|
||||
"ldap-enabled": string;
|
||||
systemApiUrl: string;
|
||||
systemPcUrl: string;
|
||||
systemH5Url: string;
|
||||
systemLogo: string;
|
||||
@@ -14,11 +13,11 @@ type SystemConfigStoreInterface = {
|
||||
playerBulletSecretText: string;
|
||||
playerBulletSecretColor: string;
|
||||
playerBulletSecretOpacity: string;
|
||||
resourceUrl?: ResourceUrlModel;
|
||||
};
|
||||
|
||||
let defaultValue: SystemConfigStoreInterface = {
|
||||
"ldap-enabled": "",
|
||||
systemApiUrl: "",
|
||||
systemPcUrl: "",
|
||||
systemH5Url: "",
|
||||
systemLogo: "",
|
||||
@@ -30,6 +29,7 @@ let defaultValue: SystemConfigStoreInterface = {
|
||||
playerBulletSecretText: "",
|
||||
playerBulletSecretColor: "",
|
||||
playerBulletSecretOpacity: "",
|
||||
resourceUrl: {},
|
||||
};
|
||||
|
||||
const systemConfigSlice = createSlice({
|
||||
|
||||
@@ -11,6 +11,7 @@ type UserStoreInterface = {
|
||||
user: null;
|
||||
departments: string[];
|
||||
currentDepId: number;
|
||||
resourceUrl: ResourceUrlModel;
|
||||
isLogin: boolean;
|
||||
};
|
||||
|
||||
@@ -18,6 +19,7 @@ let defaultValue: UserStoreInterface = {
|
||||
user: null,
|
||||
departments: [],
|
||||
currentDepId: Number(getDepKey()) || 0,
|
||||
resourceUrl: {},
|
||||
isLogin: false,
|
||||
};
|
||||
|
||||
@@ -30,6 +32,7 @@ const loginUserSlice = createSlice({
|
||||
loginAction(stage, e) {
|
||||
stage.value.user = e.payload.user;
|
||||
stage.value.departments = e.payload.departments;
|
||||
stage.value.resourceUrl = e.payload.resource_url;
|
||||
stage.value.isLogin = true;
|
||||
if (e.payload.departments.length > 0 && !getDepKey()) {
|
||||
stage.value.currentDepId = e.payload.departments[0].id;
|
||||
@@ -41,6 +44,7 @@ const loginUserSlice = createSlice({
|
||||
stage.value.departments = [];
|
||||
stage.value.isLogin = false;
|
||||
stage.value.currentDepId = 0;
|
||||
stage.value.resourceUrl = {};
|
||||
clearToken();
|
||||
clearDepKey();
|
||||
clearDepName();
|
||||
|
||||
Reference in New Issue
Block a user