Commit 732761b3 by yuananting

Merge branch 'master' into feature/yuananting/20210327/test-paper

parents bb146a01 b6a08b23
......@@ -5,7 +5,7 @@
"homepage": "./",
"dependencies": {
"@antv/data-set": "^0.11.8",
"@antv/g2": "^3.5.13",
"@antv/g2": "^3.5.17",
"@babel/core": "7.9.0",
"@babel/plugin-transform-typescript": "^7.11.0",
"@babel/preset-typescript": "^7.10.4",
......@@ -31,7 +31,7 @@
"babel-plugin-jsx-control-statements": "^4.1.0",
"babel-plugin-named-asset-import": "^0.3.6",
"babel-preset-react-app": "^9.1.2",
"bizcharts": "^4.1.7",
"bizcharts": "^3.3.0",
"camelcase": "^5.3.1",
"case-sensitive-paths-webpack-plugin": "2.3.0",
"classnames": "^2.2.6",
......
@font-face {
font-family: 'iconfont'; /* project id 2223403 */
src: url('//at.alicdn.com/t/font_2223403_droqalespbg.eot');
src: url('//at.alicdn.com/t/font_2223403_droqalespbg.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_2223403_droqalespbg.woff2') format('woff2'),
url('//at.alicdn.com/t/font_2223403_droqalespbg.woff') format('woff'),
url('//at.alicdn.com/t/font_2223403_droqalespbg.ttf') format('truetype'),
url('//at.alicdn.com/t/font_2223403_droqalespbg.svg#iconfont') format('svg');
src: url('//at.alicdn.com/t/font_2223403_vzf1rkr5ya.eot');
src: url('//at.alicdn.com/t/font_2223403_vzf1rkr5ya.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_2223403_vzf1rkr5ya.woff2') format('woff2'),
url('//at.alicdn.com/t/font_2223403_vzf1rkr5ya.woff') format('woff'),
url('//at.alicdn.com/t/font_2223403_vzf1rkr5ya.ttf') format('truetype'),
url('//at.alicdn.com/t/font_2223403_vzf1rkr5ya.svg#iconfont') format('svg');
}
.iconfont{
font-family:"iconfont" !important;
......
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-13 10:57:14
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-13 17:16:44
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import PropTypes from 'prop-types';
import React from 'react'
// import { Modal } from 'antd';
import './TableSelectedData.less';
class TableSelectedData extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className={this.props.className+' selected-data-box'}>
<span className="icon iconfont">&#xe61d;</span>
<span className="selected-text">{'已选择'+this.props.selectedNum+'项'}</span>
<span className="click-clear" onClick={this.props.clearSelectedData}>清空</span>
</div>
)
}
}
TableSelectedData.propTypes = {
className: PropTypes.string, // class
selectedNum: PropTypes.number, // 已选择人数
clearSelectedData: PropTypes.func, // 取消全部选择
};
TableSelectedData.defaultProps = {
className: '',
selectedNum: 0,
clearSelectedData: function () {
}
}
export default TableSelectedData
\ No newline at end of file
.selected-data-box {
display: inline-block;
min-width: 186px;
height: 32px;
line-height: 32px;
background: #FFF4DD;
padding: 0px 16px;
border-radius:4px;
margin: 16px 10px 13px 0;
.iconfont {
color: #FF9D14;
}
.selected-text {
margin-left: 10px;
margin-right: 20px;
}
.click-clear {
float: right;
cursor: pointer;
color: #5289FA;
&:hover {
color: rgba(85, 168, 253, 0.8);
}
}
}
\ No newline at end of file
.header-box {
padding: 30px 16px;
margin: 0 16px 16px;
margin-bottom: 8px;
background: #ffffff;
.course-type {
display: inline-block;
border-radius: 2px;
padding: 0 8px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.course-name {
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 22px;
margin-left: 8px;
}
}
import React from "react";
import { Tag } from 'antd'
import "./WatchDataHeader.less";
interface WatchDataHeaderProps {
type: string;
color?: string;
courseName?: string;
className?: string;
}
const WatchDataHeader = (props: WatchDataHeaderProps) => {
const { type, color , className ,courseName} = props;
return (
<div className={className + " header-box"}>
<span className="course-type" style={{color:color,border:`1px solid ${color}`}}>{type}</span>
<span className='course-name'>{courseName}</span>
</div>
);
};
export default WatchDataHeader;
......@@ -659,7 +659,7 @@ td.ant-table-column-sort{
}
&:focus,
&:active{
color:#C6C6C6 !important;
color:#666 !important;
border:1px solid #C6C6C6 !important;
}
}
......
/*
* @Author: wufan
* @Date: 2021-03-26 15:40:49
* @LastEditors: wufan
* @LastEditTime: 2021-03-26 16:27:52
* @Description: 阿里云埋点
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
window.WEBTRACING = (eventKey, eventName, content) => {
if (!window.logger) {
return
}
const { xmState } = window.ctx;
const {storeList = []} = xmState;
const {id:storeId, storeUserId, storeName, storeType} = storeList[0] || {};
logger.push("platform", "pc");
logger.push("event", eventKey);
logger.push("eventName", eventName);
logger.push("device", navigator.userAgent);
logger.push("storeId", storeId);
logger.push("storeName", storeName);
logger.push("storeType", storeType);
logger.push("storeUserId", storeUserId);
logger.push('env', process.env.DEPLOY_ENV || 'local'); // 哪个环境的埋点
const _content = Object.assign({}, content, {
店铺名称: storeName,
店铺类型: storeType,
storeId,
storeUserId,
});
logger.push("content", JSON.stringify(_content));
// 记录日志
logger.logger();
}
/*
* @Author: wufan
* @Date: 2020-12-12 11:57:10
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-05 17:20:29
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-22 13:54:20
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -88,3 +88,9 @@ export function videoScheduleBasePage(params: object) {
export function relatedCourseToPlan(params: object) {
return Service.Hades("public/hades/relatedCourseToPlan", params);
}
export function knowledgeMediaCoursePage(params: object) {
return Service.Hades("public/hades/knowledgeMediaCoursePage", params);
}
export function getCategoryTree(params: object) {
return Service.Hades("public/hades/queryKnowledgeCategoryTree", params);
}
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-19 18:09:35
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-30 09:51:00
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from "@/common/js/service";
class KnowledgeAPI {
// 媒体课列表(视频/图文)
knowledgeMediaCoursePage = (params: object) => {
return Service.Hades("public/hades/knowledgeMediaCoursePage", params);
}
// 直播课列表
knowledgeLiveCoursePage = (params: object) => {
return Service.Hades("public/hades/knowledgeLiveCoursePage", params);
}
// 知识库分类树
getCategoryTree = (params: object) => {
return Service.Hades("public/hades/queryKnowledgeCategoryTree", params);
}
// 查询题目分类树
queryQuestionCategoryTree = (params: object) => {
return Service.Hades("public/hades/queryQuestionCategoryTree", params);
}
// 新增知识
addKnowledge = (params: object) => {
return Service.Hades("public/hades/batchAddKnowledge", params);
}
// 不同类型一起新增知识
addDifTypeKnowledge = (params: object) => {
return Service.Hades("public/hades/batchAddDifTypeKnowledge", params);
}
// 删除知识
delKnowledge = (params: object) => {
return Service.Hades("public/hades/delKnowledge", params);
}
// 移动知识
moveKnowledge = (params: object) => {
return Service.Hades("public/hades/moveKnowledge", params);
}
// 知识库列表
queryPageKnowledgeForManager = (params: object) => {
return Service.Hades("public/hades/queryPageKnowledgeForManager", params);
}
// 直播课学生观看记录
queryPageKnowledgeLiveStudentVisitData = (params: object) => {
return Service.Hades("public/hades/queryPageKnowledgeLiveStudentVisitData", params);
}
// 直播课老师观看记录
queryPageKnowledgeLiveTeacherVisitData = (params: object) => {
return Service.Hades("public/hades/queryPageKnowledgeLiveTeacherVisitData", params);
}
// 分页图文课观看记录
queryPageKnowledgeMediaCourseWatchInfo = (params: object) => {
return Service.Hades("public/hades/queryPageKnowledgeMediaCourseWatchInfo", params);
}
// 直播课回放学生观看记录
queryPageKnowledgeReplayRecordPage = (params: object) => {
return Service.Hades("public/hades/queryPageKnowledgeReplayRecordPage", params);
}
// 分页获取知识库包括(子分类)
queryPageKnowledgeInclude = (params: object) => {
return Service.Hades("anon/customerHades/queryPageKnowledgeInclude", params);
}
// 分页云盘资料查看记录
queryPageKnowledgeFolderWatchInfo = (params: object) => {
return Service.Hades("public/hades/queryPageKnowledgeFolderWatchInfo", params);
}
// 资料观看记录导出
exportFolderLearnSync = (params: object) => {
return Service.Hades("public/knowledge/exportFolderLearnSync", params);
}
// 直播观看记录导出
exportLiveLearnSync = (params: object) => {
return Service.Hades("public/knowledge/exportLiveLearnSync", params);
}
//图文课观看记录导出
exportPicLearnSync = (params: object) => {
return Service.Hades("public/knowledge/exportPicLearnSync", params);
}
// 视频课观看记录导出
exportVideoLearnSync = (params: object) => {
return Service.Hades("public/knowledge/exportVideoLearnSync", params);
}
}
export default new KnowledgeAPI()
\ No newline at end of file
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-03-31 18:26:22
* @LastEditTime: 2021-04-15 21:42:47
* @LastEditors: yuananting
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
......
/*
* @Author: wufan
* @Date: 2020-11-25 18:25:02
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-05 17:20:56
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-22 13:58:04
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import {
fetchLecturerData, fetchUserData, exportStudentCourseData, exportPlayBackCourseData, fetchPlaybackList, createLiveCloudCourse, getLiveCloudCoursePage,
fetchLecturerData, getCategoryTree, knowledgeMediaCoursePage, fetchUserData, exportStudentCourseData, exportPlayBackCourseData, fetchPlaybackList, createLiveCloudCourse, getLiveCloudCoursePage,
getLiveCloudCourseDetail, updateLiveCloudCourse, turnOnOrOffLiveCloudCourse, delLiveCloudCourse, changeVideoShelfState, createVideoSchedule, delVideoSchedule,
editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode,getLiveCloudCourseBasePage,videoScheduleBasePage,relatedCourseToPlan
editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode, getLiveCloudCourseBasePage, videoScheduleBasePage, relatedCourseToPlan
} from '@/data-source/course/request-api';
export default class courseService {
......@@ -85,13 +85,20 @@ export default class courseService {
static videoWatchInfo(params: any) {
return videoWatchInfo(params);
}
static getLiveCloudCourseBasePage(params: any){
static getLiveCloudCourseBasePage(params: any) {
return getLiveCloudCourseBasePage(params);
}
static videoScheduleBasePage(params: any){
static videoScheduleBasePage(params: any) {
return videoScheduleBasePage(params);
}
static relatedCourseToPlan(params: any){
static relatedCourseToPlan(params: any) {
return relatedCourseToPlan(params);
}
static knowledgeMediaCoursePage(params: any) {
return knowledgeMediaCoursePage(params);
}
static getCategoryTree(params: any) {
return getCategoryTree(params);
}
}
\ No newline at end of file
<!--
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 19:34:13
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-02 14:58:57
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_325yz7wxu2d.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_vzf1rkr5ya.css">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
......
<!--
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: yuananting
* @LastEditTime: 2021-03-24 19:34:26
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-02 14:59:11
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_325yz7wxu2d.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_vzf1rkr5ya.css">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
......@@ -42,6 +42,7 @@
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/lrz.all.bundle.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/PhotoClip.js"></script>
<script type="text/javascript" charset="utf-8" src="//g.alicdn.com/sd/ncpc/nc.js?t=2015052012"></script>
<script type="text/javascript" src="https://xiaomai-js.oss-cn-hangzhou.aliyuncs.com/loghub-xm-0.0.1-beta.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-04-27 20:35:34
* @LastEditors: wufan
* @LastEditTime: 2020-12-23 19:38:35
* @LastEditTime: 2021-03-26 16:14:09
* @Description:
*/
......@@ -18,6 +18,7 @@ import 'antd/dist/antd.less';
import 'video-react/dist/video-react.css';
import '@/common/less/index.less';
import '@/core/function';
import '@/core/xmTD';
import User from '@/common/js/user';
import Service from "@/common/js/service";
......
/*
* @Author: 吴文洁
* @Date: 2020-07-15 17:29:12
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-15 18:16:05
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-29 16:19:49
* @Description: 新建/编辑直播课-基本信息
*/
......@@ -241,7 +241,7 @@ class AddLiveBasic extends React.Component {
className={`default-btn ${isDefaultCover ? 'disabled' : ''}`}
onClick={this.handleResetCoverUrl}
>使用默认图</span>
<div className="tips">建议尺寸1280*720像素,图片支持jpg、jpeg、png格式。</div>
<div className="tips">建议尺寸1280*720px,图片支持jpg、jpeg、png格式。</div>
</div>
</div>
</div>
......
......@@ -17,13 +17,13 @@
}
.editor-box {
height: 100%;
width: 100%;
}
.w-e-toolbar {
background-color: #fff !important;
border: none !important;
border-bottom: 1px solid #E8E8E8 !important;
max-width: 700px;
}
.w-e-text-container {
......
......@@ -185,7 +185,7 @@ class LiveCourseFilter extends React.Component {
<span>讲师:</span>
<Select
placeholder="请选择讲师"
style={{ width: 240}}
style={{ width: "calc(100% - 70px)"}}
showSearch
allowClear
filterOption={(input, option) => option}
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-04 10:26:07
* @LastEditTime: 2021-03-29 14:08:47
* @Description: 图文课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -206,11 +206,15 @@ class AddGraphicsCourse extends React.Component {
cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
RCHistory.goBack();
window.RCHistory.push({
pathname: `/graphics-course`,
});
}
});
}else{
RCHistory.goBack();
window.RCHistory.push({
pathname: `/graphics-course`,
});
}
}
......@@ -452,7 +456,9 @@ class AddGraphicsCourse extends React.Component {
Service.Hades('public/hades/createMediaCourse', commonParams).then((res) => {
if (!res) return;
message.success("新建成功");
window.RCHistory.goBack();
window.RCHistory.push({
pathname: `/graphics-course`,
});
})
} else {
const editParams = {
......@@ -462,7 +468,9 @@ class AddGraphicsCourse extends React.Component {
Service.Hades('public/hades/editMediaCourse', editParams).then((res) => {
if (!res) return;
message.success("保存成功");
window.RCHistory.goBack();
window.RCHistory.push({
pathname: `/graphics-course`,
});
});
}
}
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:45
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-01 16:34:11
* @LastEditTime: 2021-03-27 16:24:47
* @Description: 视频课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -33,7 +33,9 @@ class GraphicsCourseList extends React.Component {
super(props);
this.state = {
id: '', // 视频课ID
studentIds:[]
studentIds:[],
RelatedPlanModalVisible:false,
selectPlanList:{}
}
}
......@@ -61,6 +63,17 @@ class GraphicsCourseList extends React.Component {
this.setState({ watchDataModal });
}
handlePlanName = (planArray)=>{
let planStr = "";
planArray.map((item,index)=>{
if(index < planArray.length-1){
planStr = planStr + item.planName + '、';
}else{
planStr = planStr + item.planName
}
})
return planStr
}
// 请求表头
parseColumns = () => {
const columns = [
......@@ -91,7 +104,7 @@ class GraphicsCourseList extends React.Component {
title: '课程分类',
key: 'categoryName',
dataIndex: 'categoryName',
width: '20%',
width: 120,
render: (val, record) => {
return (
<div className="record__item">
......@@ -104,7 +117,7 @@ class GraphicsCourseList extends React.Component {
title: '创建人',
key: 'createName',
dataIndex: 'createName',
width: '10%',
width: 100,
render: (val) => {
return (
<div>
......@@ -126,7 +139,7 @@ class GraphicsCourseList extends React.Component {
<span>店铺展示</span>
<Tooltip title={<div>开启后,用户可在店铺内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”店铺展示。<br/>关闭后,店铺内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
width: '12%',
width: 120,
dataIndex: "courseware",
render: (val, item, index) => {
return (
......@@ -166,6 +179,28 @@ class GraphicsCourseList extends React.Component {
}
},
{
title: '关联项',
width: 200,
key: "planList",
dataIndex: "planList",
render: (val, record) => {
return (
<div className="related-task">
{ record.relatedPlanList ?
<Tooltip title={this.handlePlanName(record.relatedPlanList)} placement="top" arrowPointAtCenter>
{ record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
})
}
</Tooltip>
:
<span></span>
}
</div>
)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
......@@ -236,7 +271,7 @@ class GraphicsCourseList extends React.Component {
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
{/* {(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
{(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<div
className="operate__item"
key="plan"
......@@ -244,7 +279,7 @@ class GraphicsCourseList extends React.Component {
this.handleRelatedModalShow(item);
}}
>关联培训计划</div>
} */}
}
<div
className="operate__item"
key="edit"
......@@ -362,7 +397,37 @@ class GraphicsCourseList extends React.Component {
};
this.props.onChange(_query);
}
handleRelatedModalShow = (item)=>{
const selectPlanList = {};
if(item.relatedPlanList){
item.relatedPlanList.map((item,index)=>{
selectPlanList[item.planId] = {}
selectPlanList[item.planId].planId = item.planId;
selectPlanList[item.planId].taskBaseVOList = [{taskId:item.taskId}];
return item
})
}
this.setState({
RelatedPlanModalVisible:true,
selectCourseId:item.id,
selectPlanList:selectPlanList
})
}
closeRelatedPlanModalVisible = ()=>{
this.setState({
RelatedPlanModalVisible:false
})
}
onChangeSelectPlanList = (selectPlanList)=>{
this.setState({
selectPlanList:selectPlanList
})
}
onConfirmSelectPlanList = ()=>{
this.setState({
RelatedPlanModalVisible:false
},()=>{this.props.onChange();})
}
render() {
const { RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state;
const { dataSource = [], totalCount, query } = this.props;
......@@ -402,6 +467,16 @@ class GraphicsCourseList extends React.Component {
onConfirm={this.onConfirmSelectPlanList}
/>
}
{ RelatedPlanModalVisible &&
<RelatedPlanModal
onClose={this.closeRelatedPlanModalVisible}
visible={RelatedPlanModalVisible}
selectCourseId={selectCourseId}
selectPlanList={selectPlanList}
onChange={this.onChangeSelectPlanList}
onConfirm={this.onConfirmSelectPlanList}
/>
}
{ this.state.shareLiveModal }
{ this.state.watchDataModal }
</div>
......
import React from 'react';
import { Select, Tooltip } from 'antd';
import { Chart } from '@antv/g2';
import DataSet from "@antv/data-set";
import { Chart as G2Chart } from '@antv/g2';
import {
G2,
Chart,
Geom,
Axis,
Tooltip as G2Tooltip,
Coord,
Label,
Legend,
View,
Guide,
Shape,
Facet,
Util
} from "bizcharts";
import moment from 'moment'
import Service from "@/common/js/service";
import User from '@/common/js/user';
......@@ -21,8 +37,11 @@ class Home extends React.Component {
liveCourseNum: 0,
totalCustomerNum: 0,
videoCourseNum: 0,
pictureCourseNum: 0,
timeRange: '7',
studyTimeRange: '7',
completeNum: 0,
unfinishedNum: 0,
}
this._chart = null;
}
......@@ -31,6 +50,15 @@ class Home extends React.Component {
this.getPanelInfo();
this.getStudyInfo();
this.getHotCourse();
this.getTrainingInfo();
}
getTrainingInfo() {
Service.Hades('public/hades/planOverview', { storeId: User.getStoreId() }).then((res) => {
if (res.success) {
this.setState(res.result)
}
});
}
getHotCourse() {
......@@ -74,10 +102,12 @@ class Home extends React.Component {
incLiveCourseNum: res.result.incLiveCourseNum,
incVideoCourseNum: res.result.incVideoCourseNum,
incPictureCourseNum: res.result.incPictureCourseNum,
incTrainingPlanNum: res.result.incTrainingPlanNum,
liveCourseNum: res.result.liveCourseNum,
totalCustomerNum: res.result.totalCustomerNum,
videoCourseNum: res.result.videoCourseNum,
pictureCourseNum: res.result.pictureCourseNum,
trainingPlanNum: res.result.trainingPlanNum,
})
}
})
......@@ -99,7 +129,7 @@ class Home extends React.Component {
createChart = (data) => {
if (!this._chart) {
this._chart = new Chart({
this._chart = new G2Chart({
container: 'chart-id',
forceFit: true,
height: 290,
......@@ -196,7 +226,39 @@ class Home extends React.Component {
timeRange,
scheduleType,
studyTimeRange,
trainingPlanNum,
incTrainingPlanNum,
unfinishedNum,
completeNum,
planCustomerNum,
} = this.state;
const data = [
{
item: '已完成培训',
count: completeNum,
}, {
item: '未完成培训',
count: unfinishedNum,
}
];
const { DataView } = DataSet;
const { Html } = Guide;
const sum = data[0].count + data[1].count;
const dv = new DataView();
sum && dv.source(data).transform({
type: "percent",
field: "count",
dimension: "item",
as: "percent"
});
const cols = {
percent: {
formatter: val => {
val = val * 100 + "%";
return val;
}
}
};
return (
<div className="home-page">
<div className="home-title">数据概况</div>
......@@ -273,13 +335,13 @@ class Home extends React.Component {
<img className="header-icon" src="https://image.xiaomaiketang.com/xm/jZf3GNY5tY.png" />
<span className="header-word">培训计划总数 (个)</span>
</div>
<div className="data-number">0</div>
<div className="data-number">{trainingPlanNum}</div>
<div className="data-footer">
<span className="footer-word">本月新增</span>
{false &&
{incTrainingPlanNum > 0 &&
<span className="icon iconfont">&#xe635;</span>
}
<span className="footer-number">0</span>
<span className="footer-number">{incTrainingPlanNum}</span>
</div>
</div>
......@@ -363,24 +425,78 @@ class Home extends React.Component {
</Tooltip>
<span className="tip">(本月)</span>
</div>
<div className="circle-box">
{(unfinishedNum || completeNum) ?
<div
className="left-graph-container"
id="mountNode"
style={{ width: '100%', marginLeft: '-20%', marginTop: -30 }}
ref={e => e && (this.width = e.clientWidth)}
>
{this.width && <div>
<Chart
height={400}
width={this.width}
data={dv}
scale={cols}
padding={20}
>
<Coord type={"theta"} radius={0.75} innerRadius={0.6} />
<Axis name="percent" />
<G2Tooltip
showTitle={false}
itemTpl="<li><span style=&quot;background-color:{color};&quot; class=&quot;g2-tooltip-marker&quot;></span>{name}: {value}</li>"
/>
<Guide>
<Html
position={['50%', "50%"]}
html={`<div style="color:#8c8c8c;font-size:14px;text-align: center;width: ${this.width}px;"><span style="color:#333;font-size:20px">${planCustomerNum}人</span><br>新增培训人数</div>`}
alignX="middle"
alignY="middle"
/>
</Guide>
<Geom
type="intervalStack"
position="percent"
color={['item', ['#FDB513', '#5289FA']]}
tooltip={[
"item*percent",
(item, percent) => {
percent = Math.round(percent * 100) + "%";
return {
name: item,
value: percent,
};
}
]}
style={{
lineWidth: 1,
stroke: "#fff"
}}
>
</Geom>
</Chart>
</div>
}
</div>
: <div className="circle-box">
<div className="big-circle">
<div className="small-circle">
<div className="tip-box">
<div style={{ color: '#333', fontSize: '20px', marginBottom: 4 }}>0</div>
<div style={{ color: '#333', fontSize: '20px', marginBottom: 4 }}>{planCustomerNum}</div>
<div style={{ color: '#999' }}>新增培训人数</div>
</div>
</div>
</div>
</div>
}
<div className="circle-tip unfinished">
<div className="spot"></div>
<div className="number">0</div>
<div className="number">{unfinishedNum}</div>
<div className="word">未完成培训</div>
</div>
<div className="circle-tip finished">
<div className="spot"></div>
<div className="number">0</div>
<div className="number">{completeNum}</div>
<div className="word">完成培训</div>
</div>
</div>
......@@ -411,6 +527,10 @@ class Home extends React.Component {
<div>人均学习时长(分钟)</div>
</div>
<div id="chart-id"></div>
<div className="chart-bottom-tip">
<div className="tip-item" style={{ marginRight: 100 }}><span className="student-dot"></span>学习人数</div>
<div className="tip-item"><span className="time-dot"></span>人均学习时长</div>
</div>
</div>
</div>
)
......
.home-page {
padding: 0 16px 16px;
min-width: 1100px;
.g2-tooltip-marker {
border-radius: 50% !important;
}
.home-title {
height: 44px;
line-height: 44px;
......@@ -282,10 +285,11 @@
}
.study-chart {
width: 100%;
height: 396px;
height: 432px;
background: #fff;
padding: 16px;
margin-top: 16px;
padding-bottom: 32px;
.study-title {
font-size: 16px;
color: #333;
......@@ -315,6 +319,29 @@
}
}
}
.chart-bottom-tip {
display: flex;
justify-content: center;
.tip-item {
display: flex;
align-items: center;
color: #666;
.student-dot {
background: #5289FA;
height: 8px;
width: 8px;
border-radius: 50%;
margin-right: 8px;
}
.time-dot {
background: #FEB613;
height: 8px;
width: 8px;
border-radius: 50%;
margin-right: 8px;
}
}
}
}
}
......
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-22 18:24:53
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-27 11:54:42
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
const ENUM = {
courseStateShow: {
UN_START: {
code: 1,
title: "待开课",
color: "#FFB714",
},
STARTING: {
code: 2,
title: "上课中",
color: "#238FFF",
},
FINISH: {
code: 3,
title: "已完成",
color: "#3BBDAA",
},
EXPIRED: {
code: 4,
title: "未成功开课",
color: "#999",
},
},
CourseTypeEnum: {
LIVE: "直播课",
VOICE: "视频课",
PICTURE: "图文课",
FOLDER: "学习资料",
},
};
export default ENUM
\ No newline at end of file
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-19 18:05:23
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-08 11:29:12
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Input, Button, Tree } from "antd";
import "./Classification.less";
import User from "@/common/js/user";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
const { Search } = Input;
const { DirectoryTree } = Tree;
class Classification extends Component {
constructor(props) {
super(props);
this.state = {
selectedKeys: props.selectedKeys ? [props.selectedKeys] : ["0"],
searchValue: null,
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
ImportCourseCategory: null, // 引用课程分类模态框
treeData: this.props.treeData || [],
autoExpandParent: false,
};
}
componentDidMount() {
this.queryCategoryTree();
}
shouldComponentUpdate = (nextProps, nextState) => {
const { updateCategoryFlag } = nextProps;
if (this.props.updateCategoryFlag !== updateCategoryFlag) {
this.queryCategoryTree();
}
return true;
};
/** 获取树状第一级key 设置默认展开第一项 */
getFirstLevelKeys = (data) => {
let firstLevelKeys = [];
data.forEach((item) => {
if (item.categoryLevel === 0) {
firstLevelKeys.push(item.key);
}
});
return firstLevelKeys;
};
/** 树状展开事件 */
onExpand = (expandedKeys) => {
this.setState({ expandedKeys });
};
/** 树状选中事件 */
onSelect = (selectedKeys) => {
this.setState({ selectedKeys });
console.log(selectedKeys);
this.props.getSelectedCategoryId(selectedKeys[0]);
};
// 查询分类树
queryCategoryTree = (categoryName) => {
let query = {
categoryName,
storeId: User.getStoreId(),
withCount: true,
};
KnowledgeAPI.getCategoryTree(query).then((res) => {
const { categoryList = [], noCategoryCnt = 0 } = res.result;
let str = "未分类";
if (categoryName) {
this.setState({ autoExpandParent: true });
if (str.indexOf(categoryName) < 0) {
this.setState({
treeData: this.renderTreeNodes(categoryList, categoryName),
});
let nodeId = [];
Object.keys(this.state.treeMap).forEach((item) => {
nodeId.push(item);
});
this.setState({ expandedKeys: nodeId });
} else {
const defaultNode = {
id: "0",
categoryName: "未分类",
categoryCount: noCategoryCnt,
};
categoryList.unshift(defaultNode);
this.setState({
treeData: this.renderTreeNodes(categoryList, categoryName),
});
let nodeId = [];
Object.keys(this.state.treeMap).forEach((item) => {
nodeId.push(item);
});
this.setState({ expandedKeys: nodeId });
}
} else {
this.setState({ autoExpandParent: false });
const defaultNode = {
id: "0",
categoryName: "未分类",
categoryCount: noCategoryCnt,
};
categoryList.unshift(defaultNode);
this.setState({
treeData: this.renderTreeNodes(categoryList, categoryName),
});
this.setState({ expandedKeys: [] });
}
});
};
getTreeMap = (data, map) => {
data.forEach((item) => {
map[item.id] = item;
if (item.sonCategoryList && item.sonCategoryList.length > 0) {
this.getTreeMap(item.sonCategoryList, map);
}
});
return map;
};
renderTreeNodes = (data, value) => {
let newTreeData = data.map((item) => {
item.title = item.categoryName;
item.key = item.id;
item.title =
!value || (value && item.categoryName.indexOf(value) > -1) ? (
<span>
{item.categoryName}{item.categoryCount}
</span>
) : (
<span style={{ opacity: 0.5 }}>
{item.categoryName}{item.categoryCount}
</span>
);
item.icon =
item.categoryName === "未分类" ? (
<img
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/defaultCategory.png"
alt=""
/>
) : (
<img
style={{
width: "24px",
height: "24px",
opacity:
!value || (value && item.categoryName.indexOf(value) > -1)
? 1
: 0.5,
}}
src="https://image.xiaomaiketang.com/xm/hasCategory.png"
alt=""
/>
);
if (item.sonCategoryList) {
item.children = this.renderTreeNodes(item.sonCategoryList, value);
}
return item;
});
let map = {};
this.setState({ treeMap: this.getTreeMap(data, map) });
return newTreeData;
};
render() {
const {
treeData,
expandedKeys,
selectedKeys,
autoExpandParent,
} = this.state;
return (
<div className="question-bank-sider">
<div className="sider-title">知识分类</div>
<Search
className="sider-search"
placeholder="搜索名称分类"
onSearch={(value) => {
// TODO 调用查询分类接口
this.queryCategoryTree(value);
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
<div className="sider-btn">
<Button
onClick={() => {
window.RCHistory.push({
pathname: "/question-category-manage?from=knowledge",
});
}}
>
分类管理
</Button>
</div>
<div className="sider-tree">
<DirectoryTree
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onExpand={this.onExpand}
selectedKeys={selectedKeys}
onSelect={this.onSelect}
showIcon
treeData={treeData}
/>
</div>
{this.state.NewEditQuestionBankCategory}
{this.state.ImportCourseCategory}
</div>
);
}
}
export default Classification;
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-19 18:16:58
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-25 19:13:06
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-bank-sider {
position: relative;
.sider-title {
height: 22px;
font-size: 16px;
font-weight: 500;
color: #000000;
line-height: 22px;
margin-bottom: 16px;
}
.sider-search {
margin-bottom: 16px;
}
.sider-btn {
margin-bottom: 16px;
}
.sider-tree {
width: 266px;
overflow: scroll;
height: ~'calc(100vh - 300px)';
.empty-tree-tip {
text-align: center;
margin-top: 100%;
.empty-tree-btn {
color: #ffb714;
cursor: pointer;
}
}
.ant-tree.ant-tree-directory {
font-size: 14px;
font-weight: 400;
color: #666666;
width: 260px;
.anticon {
color: #666666;
}
.ant-tree-treenode {
height: 44px;
padding: 0;
span {
line-height: 44px;
white-space: nowrap;
}
.ant-tree-node-content-wrapper.ant-tree-node-selected {
color: #666666;
}
}
.ant-tree-treenode-selected:hover::before,
.ant-tree-treenode-selected::before {
background: #fffbf1;
}
}
}
}
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-15 16:51:40
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-10 16:13:07
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { withRouter } from "react-router-dom";
import { Table, Popover, message, Button, Spin } from "antd";
import dealTimeDuration from "../../course-manage/utils/dealTimeDuration";
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import User from "@/common/js/user";
// import "./DataList.less";
class CourseData extends React.Component {
constructor(props) {
super(props);
const courseId = getParameterByName("id"); // 课程ID
const type = getParameterByName("type"); // 来源: 大班直播 large 互动班课 interactive
this.state = {
type,
teacherData: [], // 老师上课数据
studentData: [], // 学员上课数据
current: 1,
size: 10, // 添加扣课时学员弹窗回填所需 当前接口返回全部列表
total: 0,
loading: true,
id: courseId,
storeId: User.getStoreId(),
};
}
componentDidMount() {
this.fetchLecturerData();
this.fetchUserData();
}
// 获取学员上课数据
fetchUserData = (current = 1) => {
this.setState({ loading: true });
const { id, size } = this.state;
const params = {
id,
current,
size,
};
KnowledgeAPI.queryPageKnowledgeLiveStudentVisitData(params).then((res) => {
if (res.result) {
const { records = [], current, size, total } = res.result;
this.setState({
studentData: records,
current,
size,
total,
loading: false,
});
}
});
};
// 获取老师上课数据
fetchLecturerData = () => {
const { id } = this.state;
KnowledgeAPI.queryPageKnowledgeLiveTeacherVisitData({ id }).then((res) => {
if (res.result) {
this.setState({
teacherData: res.result,
});
}
});
};
// 进入直播次数列表
getVisiterColumns() {
const columns = [
{
title: "序号",
dataIndex: "index",
width: 50,
render: (text, record, index) => {
return <span>{index + 1}</span>;
},
},
{
title: "进入时间",
dataIndex: "entryTime",
width: 150,
render: (text) => (
<span>{formatDate("YYYY-MM-DD H:i", parseInt(text))}</span>
),
},
{
title: "离开时间",
dataIndex: "leaveTime",
width: 150,
render: (text) => (
<span>{formatDate("YYYY-MM-DD H:i", parseInt(text))}</span>
),
},
{
title: "在线时长",
dataIndex: "lookingDuration",
render: (text, record) => {
return <span>{text ? dealTimeDuration(text) : "-"}</span>;
},
},
];
return columns;
}
// 学员上课数据列表
getStudentColumns() {
const columns = [
{
title: "用户姓名",
dataIndex: "userName",
},
{
title: "手机号",
dataIndex: "phone",
render: (text, record) => {
const { phone = "", bindingWeChat } = record;
return <div>{phone}</div>;
},
},
{
title: "观看次数",
dataIndex: "entryNum",
render: (text, record) => {
if (text > 0) {
if (
record.visitorInfoVOList &&
record.visitorInfoVOList.length > 0
) {
const table = (
<Table
columns={this.getVisiterColumns()}
dataSource={record.visitorInfoVOList}
scroll={{ y: 75 }}
size={"small"}
style={{ width: 450 }}
pagination={false}
/>
);
return (
<Popover content={table} trigger="click">
<span className="handel-btn">{text}</span>
</Popover>
);
} else {
return <div className="live-table--empty">暂无观看数据</div>;
}
} else {
return <span>{text}</span>;
}
},
},
{
title: "累计在线时长",
dataIndex: "totalDuration",
sorter: (a, b) => a.totalDuration - b.totalDuration,
sortDirections: ["descend", "ascend"],
render: (text, record) => {
//如无离开时间,就置空
return <span>{text ? dealTimeDuration(text) : "00:00:00"}</span>;
},
},
];
return columns;
}
// 学员导出5.0
handleExportV5 = () => {
const { id, storeId } = this.state;
KnowledgeAPI.exportLiveLearnSync({
knowledgeId: id,
exportLiveType: "VISITOR",
storeId,
}).then((res) => {
const link = res.result;
this.setState({
link
});
document.getElementById("loadExcel").click();
if (res.success) {
message.success("导出成功!");
}
});
};
handleCheckEnterTimes = () => {
const { teacherData } = this.state;
if (teacherData.entryNum > 0) {
if (
teacherData.visitorInfoVOList &&
teacherData.visitorInfoVOList.length > 0
) {
const table = (
<Table
columns={this.getVisiterColumns()}
dataSource={teacherData.visitorInfoVOList}
scroll={{ y: 75 }}
size={"small"}
style={{ width: 450 }}
pagination={false}
/>
);
return (
<Popover content={table} trigger="click">
<span className="times-num">{teacherData.entryNum}</span>
</Popover>
);
} else {
return <div className="live-table--empty">暂无观看数据</div>;
}
} else {
return <span>{teacherData.entryNum}</span>;
}
};
showTable = () => {
return (
<Table
columns={this.getVisiterColumns()}
dataSource={this.state.teacherData.visitorInfoVOList}
scroll={{ y: 80 }}
size={"small"}
style={{ width: 450 }}
pagination={false}
className="visitor-num-table"
/>
);
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState({ size }, this.fetchUserData)
}
render() {
const {
teacherData,
studentData,
current,
size,
total,
loading,
link
} = this.state;
return (
<Spin spinning={loading}>
<a
href={link}
target="_blank"
download
id="loadExcel"
style={{ position: "absolute", left: "-10000px" }}
>
111
</a>
{/* 讲师上课数据 */}
<div className="courseData-teacher">
<p className="title">讲师上课数据</p>
<div className="teacher-course-data">
<div className="avatar-name-phone item-block">
<img
src={
teacherData.avatar ||
"https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png"
}
alt=""
className="avatar"
/>
<div className="right">
<div className="name">{teacherData.userName}</div>
<div className="phone">
{teacherData.phone}
</div>
</div>
</div>
<div className="times item-block">
<div
className={`times-num ${
Number(teacherData.entryNum) > 0 ? "can-click" : ""
}`}
>{
Number(teacherData.entryNum) > 0 ?
<Popover content={this.showTable()} trigger="click">
<span className="times-num">{teacherData.entryNum || 0}</span>
</Popover>:
<span className="times-num">{0}</span>
}
</div>
<div className="text">进入直播间次数</div>
</div>
<div className="online-duration item-block">
<div className="duration">
{teacherData.totalDuration
? dealTimeDuration(teacherData.totalDuration)
: "00:00:00"}
</div>
<div className="text">累计在线时长</div>
</div>
</div>
</div>
{/* 用户上课数据 */}
<div className="courseData-student">
<p className="title">用户上课数据</p>
<div className="filter-wrap">
<div className="filter">
<Button
style={{ height: 32}}
onClick={_.debounce(
() => {
if (!studentData.length) {
message.warning("暂无数据可导出");
return;
}
this.handleExportV5();
},
500,
true
)}
>
导出
</Button>
</div>
</div>
<Table
size="small"
scroll={{ y: 600 }}
columns={this.getStudentColumns()}
dataSource={studentData}
pagination={false}
style={{ margin: "16px 0" }}
/>
{ total > 0 &&
<PageControl
size="small"
current={current - 1}
pageSize={size}
total={total}
onShowSizeChange={this.onShowSizeChange}
toPage={(page) => {
this.fetchUserData(page + 1);
}}
/>
}
</div>
</Spin>
);
}
}
export default withRouter(CourseData);
.knowledge-base-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 33%;
max-width: 600px;
margin-right: 3%;
margin-bottom: 12px;
.search-name{
vertical-align: middle;
line-height: 30px;
}
.shelf-status{
width:84px;
display:inline-block;
text-align:right;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left:4px;
}
}
}
.data-icon {
cursor: pointer;
}
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 11:16:38
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-08 11:51:42
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Row, Input, Select, Tooltip } from "antd";
import "./KnowledgeBase.less";
import ENUM from "../ENUM.js";
const { Search } = Input;
const { Option } = Select;
const DEFAULT_QUERY = {
name: null, // 课程名称
type: null,
};
class KnowledgeBaseFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
query: { ...DEFAULT_QUERY }, // 使用扩展运算符,避免浅拷贝
};
}
// 改变搜索条件
handleChangeQuery = (field, value, flag = true) => {
this.setState(
{
query: {
...this.state.query,
[field]: value,
current: 1,
},
},
() => {
this.props.onChange(this.state.query,flag);
}
);
};
// 重置搜索条件
handleReset = () => {
this.setState(
{
query: DEFAULT_QUERY,
},
() => {
this.props.onChange(this.state.query);
}
);
};
render() {
const {
query: { name, type },
} = this.state;
return (
<div className="knowledge-base-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">课程名称:</span>
<Search
value={name}
placeholder="搜索课程名称"
onChange={(e) => {
this.handleChangeQuery("name", e.target.value, false);
}}
onSearch={(value) => {
this.handleChangeQuery("name", value);
}}
style={{ width: "calc(100% - 84px)" }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="shelf-status">课程类型:</span>
<Select
style={{ width: "calc(100% - 84px)" }}
placeholder="请选择课程类型"
allowClear={true}
value={type}
onChange={(value) => {
this.handleChangeQuery("type", value);
}}
suffixIcon={
<span
className="icon iconfont"
style={{ fontSize: "12px", color: "#BFBFBF" }}
>
&#xe835;
</span>
}
>
{Reflect.ownKeys(ENUM.CourseTypeEnum).map((item) => {
return (
<Option key={item} value={item}>{ENUM.CourseTypeEnum[item]}</Option>
);
})}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={this.handleReset}
>
&#xe61b;{" "}
</span>
</Tooltip>
</div>
</Row>
</div>
);
}
}
export default KnowledgeBaseFilter;
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 14:49:40
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-12 10:31:53
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Table, Modal, message, Tooltip, Switch, Dropdown, Button } from "antd";
import { Route, withRouter } from "react-router-dom";
import { PageControl } from "@/components";
import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass";
import { appId, shareUrl, LIVE_SHARE } from "@/domains/course-domain/constants";
import ScanFileModal from "../../resource-disk/modal/ScanFileModal";
import WatchData from "./WatchData";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import ENUM from "../ENUM.js";
import "./KnowledgeBaseList.less";
const DEFAULT_SIZE_UNIT = 1000 * 1000; // 将B转换成M
const { confirm } = Modal;
const ENV = process.env.DEPLOY_ENV || "dev";
class KnowledgeBaseList extends React.Component {
constructor(props) {
super(props);
this.state = {
id: "",
scanFileModal:null
};
}
handleAdminName = (adminArray) => {
let adminStr = "";
adminArray.map((item, index) => {
if (index < adminArray.length - 1) {
adminStr = adminStr + item.adminName + "、";
} else {
adminStr = adminStr + item.adminName;
}
});
return adminStr;
};
handleUp = (index, record) => {
if (index === 0 && this.props.query.current === 1) {
return;
}
const params = {
direction: "UP",
id: record.id,
storeId: record.storeId,
};
KnowledgeAPI.moveKnowledge(params).then((res) => {
if (res.success) {
message.success("更新成功");
this.props.onChange();
}
});
};
handleDown = (record, index) => {
const { query, totalCount } = this.props;
const { current, size } = query;
if (totalCount === size * (current - 1) + index + 1) {
return;
}
const params = {
direction: "DOWN",
id: record.id,
storeId: record.storeId,
};
KnowledgeAPI.moveKnowledge(params).then((res) => {
if (res.success) {
message.success("更新成功");
this.props.onChange();
}
});
};
getBlob = (url) => {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'blob'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
}
}
xhr.send()
})
}
saveAs = (blob, filename) => {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename)
} else {
const link = document.createElement('a')
const body = document.querySelector('body')
// 创建对象url
link.href = window.URL.createObjectURL(blob)
link.download = filename
body.appendChild(link)
link.click()
body.removeChild(link)
// 通过调用 URL.createObjectURL() 创建的 URL 对象
window.URL.revokeObjectURL(link.href)
}
}
// 下载文件
handleDownload = (folder) => {
this.getBlob(folder.ossUrl).then((blob) => {
this.saveAs(blob, folder.folderName)
})
}
// 预览文件
handleScanFile = (folder) => {
console.log(folder);
const { folderFormat, folderSize, ossUrl } = folder;
switch (folderFormat) {
case "PDF":
window.open(ossUrl, "_blank");
break;
case "WORD":
case "DOCX":
case "DOC":
case "EXCEL":
case "PPT":
case "PPTX":
case "PDF":
if (
((folderFormat === "PPT" ||
folderFormat === "PPTX" ||
folderFormat === "DOCX" ||
folderFormat === "WORD" ||
folderFormat === "DOC") &&
folderSize > 10 * DEFAULT_SIZE_UNIT) ||
(folderFormat === "EXCEL" && folderSize > 5 * DEFAULT_SIZE_UNIT)
) {
Modal.confirm({
title: "抱歉,不能在线预览",
content: "由于文件较大,不支持在线预览,请下载后再查看",
// icon: <Icon type="question-circle" theme="filled" style={{ color: '#FF8534' }}></Icon>,
cancelText: "取消",
okText: "下载",
onOk: () => {
this.handleDownload(folder)
},
});
break;
}
const prefixUrl = "https://view.officeapps.live.com/op/view.aspx?src=";
const scanUrl = `${prefixUrl}${encodeURIComponent(ossUrl)}`;
window.open(scanUrl, "_blank");
break;
default:
const scanFileModal = (
<ScanFileModal
fileType={folderFormat}
item={folder}
close={() => {
this.setState({ scanFileModal: null });
}}
/>
);
this.setState({ scanFileModal });
break;
}
};
// 请求表头
parseColumns = () => {
const { query, totalCount } = this.props;
const { current, size } = query;
const columns = [
{
title: "课程名称",
key: "name",
dataIndex: "name",
width: 391,
fixed: "left",
render: (val, record) => {
const { coverUrl, mediaCourseUrl } = record.source;
let hasCover = false;
const type = record.type;
return (
<div>
{type === "LIVE" && (
<div className="record__item">
{record.source &&
record.source.courseMediaVOS.map((item, index) => {
if (item.contentType === "COVER") {
hasCover = true;
return (
<img
className="course-cover"
key={index}
src={item.mediaUrl}
/>
);
}
})}
{!hasCover && (
<img
className="course-cover"
src={"https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"}
/>
)}
<div>
{val.length > 17 ? (
<Tooltip title={val}>
<div className="course-name">{val}</div>
</Tooltip>
) : (
<div className="course-name">{val}</div>
)}
<div>
<span className="course-time">
{formatDate(
"YYYY-MM-DD H:i",
parseInt(record.source.startTime)
)}
~{formatDate("H:i", parseInt(record.source.endTime))}
</span>
<span
className="course-status"
style={{
color:
ENUM.courseStateShow[record.source.courseState]
.color,
border: `1px solid ${
ENUM.courseStateShow[record.source.courseState]
.color
}`,
}}
>
{ENUM.courseStateShow[record.source.courseState].title}
</span>
{record.hideToUser && (
<Tooltip
title={
<div>课程未成功开课,已在用户知识列表中隐藏</div>
}
>
<i
className="icon iconfont"
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#FF4F4F",
fontSize: "14px",
}}
>
&#xe61d;
</i>
</Tooltip>
)}
</div>
<div className="teacher-assistant">
{record.source.teacherName.length > 4 ? (
<Tooltip title={record.source.teacherName}>
<span className="teacher">
讲师:{record.source.teacherName}
</span>
</Tooltip>
) : (
<span className="teacher">
讲师:{record.source.teacherName}
</span>
)}
{record.source.admins.length > 0 && (
<>
<span className="split"> | </span>
{this.handleAdminName(record.source.admins).length >
4 ? (
<Tooltip
title={this.handleAdminName(record.source.admins)}
>
<span className="assistant">
助教:
{record.source.admins.map((item, index) => {
return (
<span>
{item.adminName}{" "}
{index <
record.source.admins.length - 1 && (
<span></span>
)}{" "}
</span>
);
})}
</span>
</Tooltip>
) : (
<span className="assistant">
助教:
{record.source.admins.map((item, index) => {
return (
<span key={index}>
{item.adminName}{" "}
{index <
record.source.admins.length - 1 && (
<span></span>
)}{" "}
</span>
);
})}
</span>
)}
</>
)}
</div>
</div>
</div>
)}
{type === "VOICE" && (
<div className="record__item">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img
className="course-cover"
src={
coverUrl ||
`${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast`
}
/>
{val.length > 25 ? (
<Tooltip title={val}>
<div className="course-name clamp">{val}</div>
</Tooltip>
) : (
<div className="course-name clamp">{val}</div>
)}
</div>
)}
{type === "PICTURE" && (
<div className="record__item">
<img
className="course-cover"
src={
coverUrl ||
"https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"
}
/>
{val.length > 25 ? (
<Tooltip title={val}>
<div className="course-name clamp">{val}</div>
</Tooltip>
) : (
<div className="course-name clamp">{val}</div>
)}
</div>
)}
{type === "FOLDER" && (
<div
className="record__item"
onClick={() => {
this.handleScanFile(record.source);
}}
>
<div
className={`folder-type ${
record.source && record.source.folderFormat
}`}
/>
{val.length > 25 ? (
<Tooltip title={val}>
<div className="course-name clamp">{val}</div>
</Tooltip>
) : (
<div className="course-name clamp">{val}</div>
)}
</div>
)}
</div>
);
},
},
{
title: "课程类型",
key: "type",
dataIndex: "type",
align:'center',
// width: 100,
render: (val, record) => {
return (
<div className="">
{val ? ENUM.CourseTypeEnum[val] : "-"}
</div>
);
},
},
{
title: "创建人",
key: "createName",
dataIndex: "createName",
align:'center',
render: (val) => {
return (
<div>
{val && (
<Tooltip title={val}>
<div>{val.length > 4 ? `${val.slice(0, 4)}...` : val}</div>
</Tooltip>
)}
</div>
);
},
},
{
title: "观看用户数",
key: "watchUserCount",
dataIndex: "watchUserCount",
align:'right',
render: (val, item) => {
return val ? (
<div
className="operate"
style={{display:'block'}}
onClick={() => this.handleLinkToClassData(item)}
>
<span className="operate__item">{val}</span>
</div>
) : (
0
);
},
},
{
title: "",
width: 48,
},
{
title: "操作",
key: "operate",
dataIndex: "operate",
width: 160,
fixed: "right",
render: (val, record, index) => {
console.log(this.props.categoryId);
return this.props.categoryId === "0" ? (
<div className="operate">
<div
className="operate__item"
onClick={() => this.handleDelete(record)}
>
移出
</div>
</div>
) : (
<div className="operate">
<div
className={
index === 0 && current === 1
? "operate__item disable"
: "operate__item"
}
onClick={() => this.handleUp(index, record)}
>
上移
</div>
<span className="operate__item split"> | </span>
<div
className={
totalCount === size * (current - 1) + index + 1
? "operate__item disable"
: "operate__item"
}
onClick={() => this.handleDown(record, index)}
>
下移
</div>
<span className="operate__item split"> | </span>
<div
className="operate__item"
onClick={() => this.handleDelete(record)}
>
移出
</div>
</div>
);
},
},
];
return columns;
};
handleDelete = (record) => {
return confirm({
title: "移出知识",
content: "确定将分类中此知识移出吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
width: 440,
height: 188,
onOk: () => {
this.deleteConfirm(record);
},
});
};
// 前往上课数据页面
handleLinkToClassData = (item) => {
const { match } = this.props;
console.log(item);
localStorage.setItem("WatchData_CourseName", item.name);
window.RCHistory.push({
// pathname: `${match.url}/course-data?type=${item.courseType}&id=${item.liveCourseId}`,
pathname: `${match.url}/course-data?type=${item.type}&id=${item.id}`,
});
};
deleteConfirm = (item) => {
const params = {
id: item.id,
storeId: item.storeId,
};
KnowledgeAPI.delKnowledge(params).then((res) => {
if (res.success) {
message.success("移出成功");
this.props.onChange();
this.props.updateCategoryTree();
}
});
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
let _query = this.props.query;
_query.size = size;
this.props.onChange(_query);
};
render() {
const { dataSource = [], totalCount, query, match } = this.props;
const { current, size } = query;
return (
<div className="knowledge-base-list">
<Table
rowKey={(record) => record.id}
size="middle"
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
scroll={{ x: 900 }}
bordered
className="knowledge-list-table"
/>
<div className="box-footer">
{totalCount > 0 && (
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.props.onChange(_query);
}}
onShowSizeChange={this.onShowSizeChange}
/>
)}
</div>
<Route path={`${match.url}/course-data`} component={WatchData} />
{this.state.scanFileModal}
</div>
);
}
}
export default withRouter(KnowledgeBaseList);
.knowledge-base-list {
margin-top: 12px;
.knowledge-list-table{
tbody {
tr{
&:nth-child(even){
background: transparent !important;
td{
background:#FFF !important;
}
}
&:nth-child(odd){
background: #FAFAFA !important;
td{
background: #FAFAFA !important;
}
}
&:hover{
td{
background:#F3f6fa !important;
}
}
}
}
}
.categoryName {
font-size: 14px;
color: #666666;
line-height: 20px;
}
.courseware {
font-size: 14px;
color: #5289fa;
line-height: 20px;
text-align: right;
cursor: pointer;
}
.quota-icon {
color: #5289fa;
cursor: pointer;
}
.operate {
display: flex;
align-items: center;
flex-wrap: wrap;
.operate__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
&.disable {
cursor: auto;
color: #cccccc;
}
}
}
.operate-text {
color: #5289fa;
cursor: pointer;
}
.course-start-end {
margin-left: 16px;
width: 78px;
height: 20px;
border-radius: 2px;
border: 1px solid rgba(204, 204, 204, 1);
display: flex;
align-items: center;
cursor: pointer;
white-space: nowrap;
.start-icon {
color: #3296fa;
font-size: 12px;
transform: scale(0.8);
margin: 0 5px;
}
.end-icon {
color: #00d700;
font-size: 12px;
transform: scale(0.8);
margin: 0 5px;
}
.start-end-text {
font-size: 12px;
}
}
}
.live-course-more-menu {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
div {
line-height: 30px;
padding: 0 15px;
cursor: pointer;
&:hover {
background: #f3f6fa;
}
}
}
.tipTitle {
.type {
font-weight: 700;
}
}
.knowledge-base-list {
.record__item {
display: flex;
// align-items: center;
.course-cover {
min-width: 107px;
max-width: 90px;
height: 60px;
border-radius: 6px;
margin-right: 8px;
}
.folder-type {
min-width: 107px;
max-width: 90px;
height: 60px;
border-radius: 6px;
margin-right: 8px;
background-image: url("https://image.xiaomaiketang.com/xm/YNfi45JwFA.png");
background-size: cover;
&.JPG,&.PNG,&.JPEG {
background-image: url("https://image.xiaomaiketang.com/xm/AdpWiMHMet.png");
}
&.PPT {
background-image: url("https://image.xiaomaiketang.com/xm/eDDEZiDwMt.png");
}
&.WORD {
background-image: url("https://image.xiaomaiketang.com/xm/JGeWiiZ7YE.png");
}
&.EXCEL {
background-image: url("https://image.xiaomaiketang.com/xm/p3fDEy63Mc.png");
}
&.PDF {
background-image: url("https://image.xiaomaiketang.com/xm/2W5QAbRQfZ.png");
}
}
.course-name {
font-size: 14px;
font-weight: 500;
color: #333333;
line-height: 20px;
font-weight: bold;
font-family: PingFangSC-Medium, PingFang SC;
max-width: 244px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 20px;
&.clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
white-space: normal;
height: 40px;
}
}
.course-time {
font-size: 12px;
font-weight: 400;
color: #666666;
line-height: 20px;
}
.course-status {
font-size: 12px;
line-height: 18px;
display: inline-block;
border-radius: 2px;
padding: 0 8px;
margin-left: 4px;
}
.teacher-assistant {
display: flex;
.teacher {
font-size: 12px;
color: #666666;
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
padding-top: 2px;
}
.assistant {
font-size: 12px;
color: #666666;
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
padding-top: 2px;
}
.split {
margin: 0 4px;
color: #bfbfbf;
display: inline-blcok;
}
}
}
}
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 14:25:52
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-10 11:23:08
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from "react";
import { Button, Menu, Dropdown,message } from "antd";
import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal";
import { DownOutlined } from "@ant-design/icons";
import AddCourse from "../modal/AddCourse";
import User from "@/common/js/user";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
export default function KnowledgeBaseOpt({
categoryId,
updateCategoryTree,
onChange,
}) {
const [modal, setModal] = useState(null);
const menu = (
<Menu>
<Menu.Item key="1" style={{ textAlign: "center" }}>
<span onClick={handAddCourse}>添加课程</span>
</Menu.Item>
<Menu.Item key="2" style={{ textAlign: "center" }}>
<span onClick={handleAddFile}>添加资料</span>
</Menu.Item>
</Menu>
);
function handAddCourse() {
let modal = (
<AddCourse
onClose={() => {
setModal(null);
}}
onChange={onChange}
categoryId={categoryId}
updateCategoryTree={updateCategoryTree}
></AddCourse>
);
setModal(modal);
}
function handUpload(refIds) {
const params = {
categoryId: categoryId,
refIds,
storeId: User.getStoreId(),
createId: User.getStoreUserId(),
type: "FOLDER",
};
KnowledgeAPI.addKnowledge(params).then(({ success }) => {
if (success) {
message.success("新增成功");
onChange();
updateCategoryTree();
setModal(null);
}
});
setModal(null);
}
function handleAddFile() {
let modal = (
<SelectPrepareFileModal
multiple={true}
scene="knowledge"
operateType="select"
isOpen={true}
accept=".ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls"
tooltip="支持文件类型:ppt、word、excel、pdf、jpg、jpeg、png"
selectTypeList={["JPG", "JPEG", "PNG",'DOC','PDF','EXCEL','application/msword','application/vnd.ms-powerpoint']} // DOC 包含 .pptx,.docx,.xls.XLSX,WORD:DOC
onClose={() => {
setModal(null);
}}
onSelect={handUpload}
/>
);
setModal(modal);
}
return (
<div className="video-course-opt">
<Dropdown overlay={menu}>
<Button type="primary" className="mr12">
添加知识
<DownOutlined />
</Button>
</Dropdown>
{modal}
</div>
);
}
import React from 'react';
import { withRouter } from "react-router-dom";
import { Table, Button, Modal, message } from 'antd';
import dealTimeDuration from "../../course-manage/utils/dealTimeDuration";
import { PageControl } from "@/components";
// import './DataList.less';
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import User from '@/common/js/user';
const liveTypeMap = {
USER: "普通用户",
ANCHOR: "讲师",
ADMIN: "管理员(助教)",
GUEST: "游客"
};
class PlaybackData extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id"); // 课程ID
this.state = {
playbackData: [],
current: 1,
size: 10,
total: 0,
id: id,
storeId: User.getStoreId()
}
}
componentDidMount() {
this.fetchPlaybackList();
}
fetchPlaybackList = (page = 1) => {
const { size, id } = this.state
const params = {
id,
current: page,
size
}
KnowledgeAPI.queryPageKnowledgeReplayRecordPage(params).then((res) => {
if (res.result) {
const { records = [], current, size, total } = res.result;
this.setState({
playbackData: records,
current,
size,
total
});
}
});
};
getPlaybackColumns() {
const columns = [
{
title: "观看用户",
dataIndex: "userName",
},
{
title: "手机号",
dataIndex: "phone"
},
{
title: "观看者类型",
dataIndex: "userRole",
render: (text) => <span>{liveTypeMap[text]}</span>,
},
{
title: "开始观看时间",
dataIndex: "entryTime",
render: (text) => (
<span>{text ? formatDate("YYYY-MM-DD H:i", parseInt(text)) : '-'}</span>
),
},
{
title: "观看时长",
dataIndex: "lookingDuration",
render: (text) => {
return <span>{text ? dealTimeDuration(text) : '-'}</span>;
},
},
];
return columns;
}
// 导出
handleplaybackExport() {
const { storeId, id } = this.state;
KnowledgeAPI.exportLiveLearnSync({
knowledgeId: id,
exportLiveType: "PLAY_BACK",
storeId
}).then((res) => {
const link = res.result;
this.setState({
link
});
document.getElementById("load-play-back-excel").click();
if(res.success){
message.success("导出成功!")
}
})
}
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState({ size }, this.fetchUserData)
}
render() {
const { playbackData, total, current, size, link} = this.state
return (
<div>
<a
href={link}
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
>
111
</a>
<Button onClick={() => {this.handleplaybackExport()}}>导出</Button>
<Table
bordered
size="small"
columns={this.getPlaybackColumns()}
dataSource={playbackData}
pagination={false}
style={{ margin: '16px 0' }}>
</Table>
{ total > 0 &&
<PageControl
size="small"
current={current - 1}
pageSize={size}
total={total}
onShowSizeChange={this.onShowSizeChange}
toPage={(page) => {
this.fetchPlaybackList(page + 1);
}}
/>
}
</div>
)
}
}
export default withRouter(PlaybackData);
\ No newline at end of file
import React from "react";
import { withRouter } from "react-router-dom";
import { Tabs } from "antd";
import Breadcrumbs from "@/components/Breadcrumbs";
import WatchDataHeader from "@/components/WatchDataHeader";
import CourseData from "./CourseData";
import PlaybackData from "./PlayBackData";
import WatchDataModal from "./WatchDataModal";
// import './WatchData.less';
import ENUM from '../ENUM'
class WatchData extends React.Component {
constructor(props) {
super(props);
const type = getParameterByName("type");
const id = getParameterByName("id");
this.state = {
title: type === 'LIVE' ? "上课数据" : '观看数据',
type,
id
};
}
handChangeTitle = (title) => {
this.setState({
title,
});
};
render() {
const { type, title, id } = this.state;
return (
<div className="page data-list">
<Breadcrumbs
navList={title}
goBack={() => {
RCHistory.goBack();
}}
/>
<WatchDataHeader
type={ENUM.CourseTypeEnum[type]}
courseName={localStorage.getItem("WatchData_CourseName")}
color="#FFB714"
/>
<div className="box">
{type === "LIVE" && (
<Tabs defaultActiveKey="上课记录" onChange={this.handChangeTitle}>
<Tabs.TabPane tab="上课记录" key="上课记录">
<CourseData></CourseData>
</Tabs.TabPane>
<Tabs.TabPane tab="回放记录" key="观看数据">
<PlaybackData></PlaybackData>
</Tabs.TabPane>
</Tabs>
)}
{type !== "LIVE" && (
<WatchDataModal type="videoCourseList" id={id} type={type}/>
)}
</div>
</div>
);
}
}
export default withRouter(WatchData);
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-16 10:18:31
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-30 10:17:59
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Table, message, Input, Button } from "antd";
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import User from "@/common/js/user";
// import './WatchDataModal.less';
import dealTimeDuration from "../../course-manage/utils/dealTimeDuration";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
const { Search } = Input;
class WatchDataModal extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
size: 10,
query: {
current: 1,
},
totalCount: 0,
};
}
componentDidMount() {
this.handleFetchDataList();
}
// 获取观看视频数据列表
handleFetchDataList = () => {
const { query, size, totalCount } = this.state;
const { id, type } = this.props;
const params = {
...query,
size,
id,
storeId: User.getStoreId(),
};
type === "FOLDER"
? KnowledgeAPI.queryPageKnowledgeFolderWatchInfo(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total),
});
})
: KnowledgeAPI.queryPageKnowledgeMediaCourseWatchInfo(params).then(
(res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total),
});
}
);
};
handleChangNickname = (value) => {
const isPhone = (value || "").match(/^\d+$/);
const { query } = this.state;
if (isPhone) {
query.phone = value;
query.nickName = null;
} else {
query.nickName = value;
query.phone = null;
}
query.current = 1;
this.setState({
query,
});
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState(
{
size,
},
() => {
this.handleFetchDataList();
}
);
};
// 请求表头
parseColumns = () => {
const { type } = this.props;
const columns = [
{
title: "观看用户",
key: "name",
dataIndex: "name",
},
{
title: "手机号",
key: "phone",
dataIndex: "phone",
},
{
title: "观看者类型",
key: "userRole",
dataIndex: "userRole",
},
{
title: "首次观看时间",
key: "firstWatch",
dataIndex: "firstWatch",
render: (val) => {
return formatDate("YYYY-MM-DD H:i", val);
},
},
];
if (type === "VOICE" || type === "PICTURE") {
columns.push({
title: "观看时长",
key: "watchDuration",
dataIndex: "watchDuration",
render: (val) => {
return <span>{val ? dealTimeDuration(val) : "00:00:00"}</span>;
},
});
}
if (type === "PICTURE") {
columns.push({
title: "学习进度",
key: "progress",
dataIndex: "progress",
render: (val) => {
return val === 100 ? "已完成" : <span>{val + "%"}</span>;
},
});
}
return columns;
};
handleExportV5 = () => {
const { query } = this.state;
const { id, type } = this.props;
const params = {
...query,
knowledgeId: id,
storeId: User.getStoreId(),
};
switch (type) {
case "FOLDER":
KnowledgeAPI.exportFolderLearnSync(params).then((res) => {
if (res.result) {
window.open(res.result);
} else {
message.error("导出失败");
}
});
break;
case "VOICE":
KnowledgeAPI.exportVideoLearnSync(params).then((res) => {
if (res.result) {
window.open(res.result);
} else {
message.error("导出失败");
}
});
break;
case "PICTURE":
KnowledgeAPI.exportPicLearnSync(params).then((res) => {
if (res.result) {
window.open(res.result);
} else {
message.error("导出失败");
}
});
break;
default:
break;
}
};
render() {
const { size, dataSource, totalCount, query } = this.state;
return (
<div className="watch-data">
<div className="search-container">
<Search
placeholder="搜索用户姓名/手机号"
style={{ width: 200 }}
onChange={(e) => {
this.handleChangNickname(e.target.value);
}}
onSearch={() => {
this.handleFetchDataList();
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="filter">
<Button
style={{ height: 32, margin: "16px auto 13px 0" }}
onClick={_.debounce(
() => {
if (!dataSource.length) {
message.warning("暂无数据可导出");
return;
}
this.handleExportV5();
},
500,
true
)}
>
导出
</Button>
</div>
<div>
<Table
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
/>
{dataSource.length > 0 && (
<div className="box-footer">
<PageControl
current={query.current - 1}
pageSize={size}
total={totalCount}
size="small"
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.setState(
{
query: _query,
},
() => {
this.handleFetchDataList();
}
);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
)}
</div>
</div>
);
}
}
export default WatchDataModal;
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 10:43:10
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-10 16:39:30
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import KnowledgeBaseFilter from "./components/KnowledgeBaseFilter";
import KnowledgeBaseOpt from "./components/KnowledgeBaseOpt";
import KnowledgeBaseList from "./components/KnowledgeBaseList";
import Classification from "./components/Classification";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import User from "@/common/js/user";
export default class KnowledgeBase extends React.Component {
constructor(props) {
super(props);
this.state = {
query: {
size: 10,
current: 1,
storeId: User.getStoreId(),
categoryId: 0,
},
dataSource: [], // 知识库列表
totalCount: 0, // 知识库数据总条数
categoryId: '0',
updateCategoryFlag: false,
};
}
componentWillMount() {
// 获取知识库列表
this.handleFetchScheduleList();
// this.getCategoryTree()
}
getSelectedCategoryId = (categoryId) => {
this.setState({
categoryId,
});
this.handleFetchScheduleList({ categoryId,current:1 });
};
// 更新分类树
updateCategoryTree = () => {
this.setState({
updateCategoryFlag: !this.state.updateCategoryFlag,
});
};
// 获取知识库列表
handleFetchScheduleList = (_query = {},flag = true) => {
const query = {
...this.state.query,
..._query,
};
// 更新请求参数
this.setState({ query });
flag && KnowledgeAPI.queryPageKnowledgeForManager(query).then((res) => {
// KnowledgeAPI.videoSchedulePage(query).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total),
});
});
};
render() {
const {
dataSource,
totalCount,
query,
categoryId,
updateCategoryFlag,
} = this.state;
return (
<div className="page">
<div className="content-header">知识库</div>
<div className="box" style={{ display: "flex" }}>
{/* 搜索模块 */}
<div className="left" style={{ width: 245 }}>
<Classification
updateCategoryFlag={updateCategoryFlag}
categoryId={categoryId}
getSelectedCategoryId={this.getSelectedCategoryId}
/>
</div>
<div
className="liner"
style={{ backgroundColor: "rgb(238, 238, 238)", width: 0.5, margin: "1px 16px 1px 2px" }}
></div>
<div className="right" style={{ width: "calc(100% - 285px)" }}>
<KnowledgeBaseFilter onChange={this.handleFetchScheduleList} />
{/* 操作模块 */}
{categoryId != 0 && <KnowledgeBaseOpt
onChange={this.handleFetchScheduleList}
updateCategoryTree={this.updateCategoryTree}
categoryId={categoryId}
/>}
{/* 知识库列表模块 */}
<KnowledgeBaseList
query={query}
categoryId={categoryId}
dataSource={dataSource}
totalCount={totalCount}
onChange={this.handleFetchScheduleList}
updateCategoryTree={this.updateCategoryTree}
/>
</div>
</div>
</div>
);
}
}
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-13 09:54:26
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-07 11:56:10
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import {
Row,
Modal,
Button,
message,
Table,
Select,
Radio,
Input,
Tabs,
Tooltip,
TreeSelect,
} from "antd";
import { PageControl } from "@/components";
import TableSelectedData from "@/components/TableSelectedData";
import CourseService from "@/domains/course-domain/CourseService";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import User from "@/common/js/user";
import "./LiveList.less";
import _ from "underscore";
import dealTimeDuration from "../../course-manage/utils/dealTimeDuration";
const { Search } = Input;
const { TabPane } = Tabs;
const courseStateShow = {
UN_START: {
code: 1,
title: "待开课",
color: "#FFB714",
},
STARTING: {
code: 2,
title: "上课中",
color: "#238FFF",
},
FINISH: {
code: 3,
title: "已完成",
color: "#3BBDAA",
},
EXPIRED: {
code: 4,
title: "未成功开课",
color: "#999",
},
};
class AddCourse extends React.Component {
constructor(props) {
super(props);
this.state = {
liveDataSource: [],
liveSize: 10,
liveQuery: {
current: 1,
excludeUsed: true,
courseType: "LIVE",
storeId: User.getStoreId(),
toRefKnowledgeCategoryId: this.props.categoryId,
},
liveTotalCount: 0,
selectLive: [], //弹窗内已选择的直播课程
videoDataSource: [],
videoSize: 10,
videoQuery: {
current: 1,
courseType: "VOICE",
excludeUsed: true,
storeId: User.getStoreId(),
toRefKnowledgeCategoryId: this.props.categoryId,
},
videoTotalCount: 0,
selectVideo: [], //弹窗内已选择的视频课程
pictureDataSource: [],
pictureSize: 10,
pictureQuery: {
current: 1,
excludeUsed: true,
courseType: "PICTURE",
storeId: User.getStoreId(),
toRefKnowledgeCategoryId: this.props.categoryId,
},
pictureTotalCount: 0,
selectPicture: [], //弹窗内已选择的图文课程
categoryList: [],
};
}
componentDidMount() {
this.handleFetchLiveList();
this.handleFetchVideoList();
this.handleFetchPictureList();
this.queryCategoryTree();
}
// 查询分类树
queryCategoryTree = (categoryName) => {
let query = {
storeId: User.getStoreId(),
withCount: false,
};
KnowledgeAPI.getCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
this.setState({
categoryList: this.renderTreeNodes(categoryList),
});
});
};
renderTreeNodes = (data) => {
let newTreeData = data.map((item) => {
item.title = (
<span>
<span className="icon iconfont" style={{ color: "#FBD140" }}>
&#xe7f1;&nbsp;
</span>
{item.categoryName}
</span>
);
item.key = item.id;
if (item.sonCategoryList) {
item.children = this.renderTreeNodes(item.sonCategoryList);
}
return item;
});
return newTreeData;
};
// 获取直播课列表
handleFetchLiveList = () => {
const { liveQuery, liveSize } = this.state;
const params = {
...liveQuery,
size: liveSize,
};
// CourseService.getLiveCloudCoursePage(params).then((res) => {
KnowledgeAPI.knowledgeLiveCoursePage(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
liveDataSource: records,
liveTotalCount: Number(total),
});
});
};
// 获取视频课列表
handleFetchVideoList = () => {
const { videoQuery, videoSize } = this.state;
const params = {
...videoQuery,
size: videoSize,
};
// CourseService.videoSchedulePage(query).then((res) => {
KnowledgeAPI.knowledgeMediaCoursePage(params).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
videoDataSource: records,
videoTotalCount: Number(total),
});
});
};
// 获取图文课列表
handleFetchPictureList = () => {
const { pictureQuery, pictureSize } = this.state;
const params = {
...pictureQuery,
size: pictureSize,
};
// CourseService.pictureSchedulePage(query).then((res) => {
KnowledgeAPI.knowledgeMediaCoursePage(params).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
pictureDataSource: records,
pictureTotalCount: Number(total),
});
});
};
onShowLiveSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState(
{
liveSize: size,
},
() => {
this.handleFetchLiveList();
}
);
};
onShowVideoSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState(
{
videoSize: size,
},
() => {
this.handleFetchVideoList();
}
);
};
onShowPictureSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState(
{
pictureSize: size,
},
() => {
this.handleFetchPictureList();
}
);
};
liveColumns = () => {
const columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip
title={<div>已加入该分类的课程不支持重复选择,因此不显示。</div>}
>
<i
className="icon iconfont"
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#bfbfbf",
fontSize: "14px",
}}
>
&#xe61d;
</i>
</Tooltip>
</span>
),
width: 371,
key: "course",
dataIndex: "courseName",
render: (val, record) => {
let hasCover = false;
return (
<div className="record__item">
{record.courseMediaVOS.map((item, index) => {
if (item.contentType === "COVER") {
hasCover = true;
return <img className="course-cover" src={item.mediaUrl} />;
}
})}
{!hasCover && (
<img
className="course-cover"
src={"https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"}
/>
)}
<div>
{record.courseName.length > 17 ? (
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
) : (
<div className="course-name">{record.courseName}</div>
)}
<div>
<span
className="course-status"
style={{
color: courseStateShow[record.courseState].color,
border: `1px solid ${
courseStateShow[record.courseState].color
}`,
}}
>
{courseStateShow[record.courseState].title}
</span>
</div>
</div>
</div>
);
},
},
{
title: "上课时间",
width: 110,
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, item) => {
return (
<span className="course-time">
{formatDate("YYYY-MM-DD", parseInt(item.startTime))} <br></br>
{formatDate("H:i", parseInt(item.startTime))}~
{formatDate("H:i", parseInt(item.endTime))}
</span>
);
},
},
{
title: "课程分类",
// width: "10%",
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, record) => {
return (
<div className="categoryName">
{record.categoryOneName}
{record.categoryTwoName ? `-${record.categoryTwoName}` : ""}
</div>
);
},
},
];
return columns;
};
videoColumns = () => {
const columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip
title={<div>已加入该分类的课程不支持重复选择,因此不显示。</div>}
>
<i
className="icon iconfont"
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#bfbfbf",
fontSize: "14px",
}}
>
&#xe61d;
</i>
</Tooltip>
</span>
),
key: "scheduleName",
dataIndex: "scheduleName",
width: 371,
render: (val, record) => {
const { coverUrl, mediaCourseUrl } = record;
return (
<div className="record__item">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img
className="course-cover"
src={
coverUrl ||
`${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast`
}
/>
{record.courseName.length > 25 ? (
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
) : (
<div className="course-name">{record.courseName}</div>
)}
</div>
);
},
},
{
title: "课程时长",
key: "videoDuration",
width: 80,
dataIndex: "videoDuration",
render: (text, item) => {
return <span>{text ? dealTimeDuration(text) : "-"}</span>;
},
},
{
title: "课程分类",
key: "categoryName",
dataIndex: "categoryName",
render: (val, record) => {
return (
<div className="record__item">
{record.categoryOneName}
{record.categoryTwoName ? `-${record.categoryTwoName}` : ""}
</div>
);
},
},
];
return columns;
};
pictureColumns = () => {
const columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip
title={<div>已加入该分类的课程不支持重复选择,因此不显示。</div>}
>
<i
className="icon iconfont"
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#bfbfbf",
fontSize: "14px",
}}
>
&#xe61d;
</i>
</Tooltip>
</span>
),
key: "scheduleName",
dataIndex: "scheduleName",
width: 371,
render: (val, record) => {
const { coverUrl } = record;
return (
<div className="record__item">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img
className="course-cover"
src={
coverUrl ||
"https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"
}
/>
{record.courseName.length > 25 ? (
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
) : (
<div className="course-name">{record.courseName}</div>
)}
</div>
);
},
},
{
title: "课程分类",
key: "categoryName",
dataIndex: "categoryName",
render: (val, record) => {
return (
<div className="record__item">
{record.categoryOneName}
{record.categoryTwoName ? `-${record.categoryTwoName}` : ""}
</div>
);
},
},
];
return columns;
};
selectLiveList = (record, selected) => {
let { selectLive } = this.state;
let _list = [];
if (
selected ||
!_.find(selectLive, (item) => item.liveCourseId == record.liveCourseId)
) {
_list = _.uniq(
selectLive.concat([record]),
false,
(item) => item.liveCourseId
);
} else {
_list = _.reject(
selectLive,
(item) => item.liveCourseId === record.liveCourseId
);
}
this.setState({ selectLive: _list });
};
selectVideoList = (record, selected) => {
console.log(record);
let { selectVideo } = this.state;
let _list = [];
if (selected || !_.find(selectVideo, (item) => item.id == record.id)) {
_list = _.uniq(selectVideo.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectVideo, (item) => item.id === record.id);
}
this.setState({ selectVideo: _list });
};
selectPictureList = (record, selected) => {
console.log(record);
let { selectPicture } = this.state;
let _list = [];
if (selected || !_.find(selectPicture, (item) => item.id == record.id)) {
_list = _.uniq(selectPicture.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectPicture, (item) => item.id === record.id);
}
this.setState({ selectPicture: _list });
};
callback(key) {
console.log(key);
}
handleChangVideoFilter = (key, value) => {
const { videoQuery } = this.state;
videoQuery[key] = value;
videoQuery.current = 1;
this.setState(
{
videoQuery,
},
() => {
this.handleFetchVideoList();
}
);
};
handleChangLiveFilter = (key, value) => {
const { liveQuery } = this.state;
liveQuery[key] = value;
liveQuery.current = 1;
this.setState(
{
liveQuery,
},
() => {
this.handleFetchLiveList();
}
);
};
handleChangPictureFilter = (key, value) => {
const { pictureQuery } = this.state;
pictureQuery[key] = value;
pictureQuery.current = 1;
this.setState(
{
pictureQuery,
},
() => {
this.handleFetchPictureList();
}
);
};
handAddCourse = () => {
const { selectVideo, selectLive, selectPicture } = this.state;
const batchAddList = [];
if (selectVideo.length) {
batchAddList.push({
categoryId: this.props.categoryId,
refIds: _.pluck(selectVideo, "id"),
storeId: User.getStoreId(),
type: "VOICE",
createId: User.getStoreUserId(),
});
}
if (selectLive.length) {
batchAddList.push({
categoryId: this.props.categoryId,
refIds: _.pluck(selectLive, "liveCourseId"),
storeId: User.getStoreId(),
type: "LIVE",
createId: User.getStoreUserId(),
});
}
if (selectPicture.length) {
batchAddList.push({
categoryId: this.props.categoryId,
refIds: _.pluck(selectPicture, "id"),
storeId: User.getStoreId(),
type: "PICTURE",
createId: User.getStoreUserId(),
});
}
KnowledgeAPI.addDifTypeKnowledge({ batchAddList }).then(({ success }) => {
if (success) {
message.success("新增成功");
this.props.onClose();
this.props.onChange();
this.props.updateCategoryTree();
}
});
};
renderFooter = () => {
const {selectVideo,selectPicture,selectLive} =this.state
return (
<div>
<Button onClick={this.props.onClose}>取消</Button>
<Button disabled={!(selectLive.length || selectVideo.length || selectPicture.length)} type="primary" onClick={this.handAddCourse}>
确定
</Button>
</div>
);
};
render() {
const {
liveDataSource,
liveSize,
liveQuery,
liveTotalCount,
selectLive,
videoDataSource,
videoSize,
videoQuery,
videoTotalCount,
selectVideo,
pictureDataSource,
pictureSize,
pictureQuery,
pictureTotalCount,
selectPicture,
categoryList,
} = this.state;
const LiveSelection = {
selectedRowKeys: _.pluck(selectLive, "liveCourseId"),
onSelect: this.selectLiveList,
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(
selectLive.concat(changeRows),
false,
(item) => item.liveCourseId
);
} else {
_list = _.reject(selectLive, (item) =>
_.find(
changeRows,
(data) => data.liveCourseId === item.liveCourseId
)
);
}
this.setState({ selectLive: _list });
},
};
const VideoSelection = {
selectedRowKeys: _.pluck(selectVideo, "id"),
onSelect: this.selectVideoList,
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(
selectVideo.concat(changeRows),
false,
(item) => item.id
);
} else {
_list = _.reject(selectVideo, (item) =>
_.find(changeRows, (data) => data.id === item.id)
);
}
this.setState({ selectVideo: _list });
},
};
const PictureSelection = {
selectedRowKeys: _.pluck(selectPicture, "id"),
onSelect: this.selectPictureList,
onSelectAll: (selected, _selectedRows, changeRows) => {
console.log(changeRows);
let _list = [];
if (selected) {
_list = _.uniq(
selectPicture.concat(changeRows),
false,
(item) => item.id
);
} else {
_list = _.reject(selectPicture, (item) =>
_.find(changeRows, (data) => data.id === item.id)
);
}
this.setState({ selectPicture: _list });
},
};
return (
<Modal
visible={true}
width={720}
title="新增课程"
footer={this.renderFooter()}
onCancel={this.props.onClose}
className="add-course-modal"
>
<Tabs defaultActiveKey="VIDEO" onChange={this.callback} centered>
<TabPane tab="视频课" key="VIDEO">
<div className="live-list">
<div>
<Row type="flex" justify="space-between" align="top">
<div>
<span style={{ lineHeight: "32px" }}>课程名称:</span>
<Search
// value={courseName}
style={{ width: "calc(100% - 75px)" }}
placeholder="搜索课程名称"
onSearch={(value) => {
this.handleChangVideoFilter("courseName", value);
}}
enterButton={
<span className="icon iconfont">&#xe832;</span>
}
/>
</div>
<div style={{ width: "50%" }}>
<span className="shelf-status">课程分类:</span>
<TreeSelect
style={{ width: "calc(100% - 75px)" }}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
treeData={categoryList}
placeholder="请选择课程类型"
allowClear
onChange={(value) => {
this.handleChangVideoFilter("categoryId", value);
}}
/>
</div>
</Row>
</div>
<TableSelectedData
selectedNum={
selectVideo.length + selectLive.length + selectPicture.length
}
clearSelectedData={() => {
this.setState({
selectVideo: [],
selectLive: [],
selectPicture: [],
});
}}
/>
<Table
rowKey={(record) => record.id}
dataSource={videoDataSource}
columns={this.videoColumns()}
size="middle"
rowSelection={VideoSelection}
pagination={false}
bordered
className="video-list-table"
style={{ maxHeight: 359, overflow: "scroll" }}
/>
<div className="box-footer">
{videoTotalCount > 0 && (
<PageControl
current={videoQuery.current - 1}
pageSize={videoSize}
total={videoTotalCount}
toPage={(page) => {
const _query = { ...videoQuery, current: page + 1 };
this.setState(
{
videoQuery: _query,
},
() => {
this.handleFetchVideoList();
}
);
}}
onShowSizeChange={this.onShowVideoSizeChange}
/>
)}
</div>
</div>
</TabPane>
<TabPane tab="直播课" key="LIVE">
<div className="live-list">
<div>
<Row type="flex" justify="space-between" align="top">
<div>
<span style={{ lineHeight: "32px" }}>课程名称:</span>
<Search
// value={courseName}
style={{ width: "calc(100% - 75px)" }}
placeholder="搜索课程名称"
// onChange={(e) => {
// this.handleChangLiveFilter(
// "courseName",
// e.target.value
// );
// }}
onSearch={(value) => {
this.handleChangLiveFilter("courseName", value);
}}
enterButton={
<span className="icon iconfont">&#xe832;</span>
}
/>
</div>
<div style={{ width: "50%" }}>
<span className="shelf-status">课程分类:</span>
<TreeSelect
style={{ width: "calc(100% - 75px)" }}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
treeData={categoryList}
placeholder="请选择课程类型"
allowClear
onChange={(value) => {
this.handleChangLiveFilter("categoryId", value);
}}
/>
</div>
</Row>
</div>
<TableSelectedData
selectedNum={
selectVideo.length + selectLive.length + selectPicture.length
}
clearSelectedData={() => {
this.setState({
selectVideo: [],
selectLive: [],
selectPicture: [],
});
}}
/>
<Table
bordered
size="middle"
pagination={false}
columns={this.liveColumns()}
rowSelection={LiveSelection}
// loading={loading}
dataSource={liveDataSource}
style={{ maxHeight: 359, overflow: "scroll" }}
rowKey={(row) => row.liveCourseId}
/>
{liveTotalCount > 0 && (
<div className="box-footer">
<PageControl
current={liveQuery.current - 1}
pageSize={liveSize}
total={parseInt(liveTotalCount)}
toPage={(page) => {
const _query = { ...liveQuery, current: page + 1 };
this.setState(
{
liveQuery: _query,
},
() => {
this.handleFetchLiveList();
}
);
}}
onShowSizeChange={this.onShowLiveSizeChange}
/>
</div>
)}
</div>
</TabPane>
<TabPane tab="图文课" key="PICTURE">
<div className="live-list">
<div>
<Row type="flex" justify="space-between" align="top">
<div>
<span style={{ lineHeight: "32px" }}>课程名称:</span>
<Search
style={{ width: "calc(100% - 75px)" }}
placeholder="搜索课程名称"
onSearch={(value) => {
this.handleChangPictureFilter("courseName", value);
}}
enterButton={
<span className="icon iconfont">&#xe832;</span>
}
/>
</div>
<div style={{ width: "50%" }}>
<span className="shelf-status">课程分类:</span>
<TreeSelect
style={{ width: "calc(100% - 75px)" }}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
treeData={categoryList}
placeholder="请选择课程类型"
allowClear
onChange={(value) => {
this.handleChangPictureFilter("categoryId", value);
}}
/>
</div>
</Row>
</div>
<TableSelectedData
selectedNum={
selectVideo.length + selectLive.length + selectPicture.length
}
clearSelectedData={() => {
this.setState({
selectVideo: [],
selectLive: [],
selectPicture: [],
});
}}
/>
<Table
bordered
size="middle"
pagination={false}
columns={this.pictureColumns()}
rowSelection={PictureSelection}
// loading={loading}
dataSource={pictureDataSource}
style={{ maxHeight: 359, overflow: "scroll" }}
rowKey={(row) => row.id}
/>
{pictureTotalCount > 0 && (
<div className="box-footer">
<PageControl
current={pictureQuery.current - 1}
pageSize={pictureSize}
total={parseInt(pictureTotalCount)}
toPage={(page) => {
const _query = { ...pictureQuery, current: page + 1 };
this.setState(
{
pictureQuery: _query,
},
() => {
this.handleFetchPictureList();
}
);
}}
onShowSizeChange={this.onShowPictureSizeChange}
/>
</div>
)}
</div>
</TabPane>
</Tabs>
</Modal>
);
}
}
export default AddCourse;
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-13 14:38:49
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-16 15:20:51
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Table, Modal, message, Dropdown, Button, Switch, Tooltip } from "antd";
import { Route, withRouter } from "react-router-dom";
import { PageControl } from "@/components";
import "./LiveList.less";
import TableSelectedData from "@/components/TableSelectedData";
import CourseService from "@/domains/course-domain/CourseService";
import User from "@/common/js/user";
import _ from "underscore";
const { confirm } = Modal;
const courseStateShow = {
UN_START: {
code: 1,
title: "待开课",
color: "#FFB714",
},
STARTING: {
code: 2,
title: "上课中",
color: "#238FFF",
},
FINISH: {
code: 3,
title: "已完成",
color: "#3BBDAA",
},
EXPIRED: {
code: 4,
title: "未成功开课",
color: "#999",
},
};
class LiveList extends React.Component {
constructor(props) {
super(props);
this.state = {
columns: [],
courseList: [], // 直播课列表
selectedRowKeys: [],
query: {
current: 1,
size: 10,
},
total: 0,
};
}
componentDidUpdate(prevProps, prevState) {
//必须写在if里面并且重新进行一次this.props !== prevProps的判断
if (
this.props.courseName !== prevProps.courseName ||
this.props.courseType !== prevProps.courseType
) {
this.handleFetchLiveList(this.props);
}
}
componentWillMount() {
this.handleFetchLiveList(this.state.query);
this.parseColumns();
}
// 获取直播课列表
handleFetchLiveList = (_query) => {
const { query } = this.state;
const params = {
...query,
..._query,
storeId: User.getStoreId(),
};
this.setState({ query: params });
CourseService.getLiveCloudCoursePage(params).then((res) => {
const {
result: { records = [], total },
} = res;
this.setState({
total,
courseList: records,
});
});
};
parseColumns = () => {
let columns;
columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip
title={<div>已加入该分类的课程不支持重复选择,因此不显示。</div>}
>
<i
className="icon iconfont"
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#bfbfbf",
fontSize: "14px",
}}
>
&#xe61d;
</i>
</Tooltip>
</span>
),
width: 371,
key: "course",
dataIndex: "courseName",
render: (val, record) => {
let hasCover = false;
return (
<div className="record__item">
{record.courseMediaVOS.map((item, index) => {
if (item.contentType === "COVER") {
hasCover = true;
return <img className="course-cover" src={item.mediaUrl} />;
}
})}
{!hasCover && (
<img
className="course-cover"
src={"https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"}
/>
)}
<div>
{record.courseName.length > 17 ? (
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
) : (
<div className="course-name">{record.courseName}</div>
)}
<div>
<span
className="course-status"
style={{
color: courseStateShow[record.courseState].color,
border: `1px solid ${
courseStateShow[record.courseState].color
}`,
}}
>
{courseStateShow[record.courseState].title}
</span>
</div>
</div>
</div>
);
},
},
{
title: "上课时间",
// width: "10%",
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, item) => {
return (
<span className="course-time">
{formatDate("YYYY-MM-DD", parseInt(item.startTime))} <br></br>
{formatDate("H:i", parseInt(item.startTime))}~
{formatDate("H:i", parseInt(item.endTime))}
</span>
);
},
},
{
title: "课程分类",
// width: "10%",
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, item) => {
return <div className="categoryName">{item.categoryName}</div>;
},
},
];
this.setState({ columns });
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
let _query = this.state.query;
_query.size = size;
this.handleFetchLiveList(_query);
};
selectVideoList = (record, selected) => {
let { selectedRowKeys } = this.state;
let _list = [];
if (
selected ||
!_.find(
selectedRowKeys,
(item) => item.liveCourseId == record.liveCourseId
)
) {
_list = _.uniq(
selectedRowKeys.concat([record]),
false,
(item) => item.liveCourseId
);
} else {
_list = _.reject(
selectedRowKeys,
(item) => item.liveCourseId === record.liveCourseId
);
}
this.setState({ selectedRowKeys: _list });
};
render() {
const {
columns,
total,
query,
courseList,
loading,
selectedRowKeys,
} = this.state;
const { current, size } = query;
const rowSelection = {
selectedRowKeys: _.pluck(selectedRowKeys, "liveCourseId"),
onSelect: this.selectVideoList,
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(
selectedRowKeys.concat(changeRows),
false,
(item) => item.liveCourseId
);
} else {
_list = _.reject(selectedRowKeys, (item) =>
_.find(
changeRows,
(data) => data.liveCourseId === item.liveCourseId
)
);
}
this.setState({ selectedRowKeys: _list });
},
};
return (
<div className="live-list">
<TableSelectedData
selectedNum={selectedRowKeys.length}
clearSelectedData={() => {
this.setState({
selectedRowKeys: [],
});
}}
/>
<Table
bordered
size="middle"
pagination={false}
columns={columns}
rowSelection={rowSelection}
loading={loading}
dataSource={courseList}
rowKey={(row) => row.liveCourseId}
/>
{total > 0 && (
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={parseInt(total)}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.handleFetchLiveList(_query);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
)}
</div>
);
}
}
export default withRouter(LiveList);
.live-list {
.record__item {
display: flex;
align-items: center;
.course-cover {
min-width: 107px;
max-width: 107px;
height: 60px;
border-radius: 6px;
margin-right: 8px;
}
.course-name {
font-size: 14px;
font-weight: 400;
color: #666666;
font-family: PingFangSC-Regular, PingFang SC;
line-height: 20px;
max-width: 244px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.course-time {
font-size: 12px;
font-weight: 400;
color: #666666;
line-height: 20px;
}
.course-status {
font-size: 12px;
line-height: 17px;
display: inline-block;
border-radius: 2px;
padding: 0 8px;
}
.teacher-assistant {
display: flex;
.teacher {
font-size: 12px;
color: #666666;
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
padding-top: 2px;
}
.assistant {
font-size: 12px;
color: #666666;
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
padding-top: 2px;
}
.split {
margin: 0 4px;
color: #bfbfbf;
display: inline-blcok;
}
}
}
.categoryName {
font-size: 14px;
color: #666666;
line-height: 20px;
}
.courseware {
font-size: 14px;
color: #5289fa;
line-height: 20px;
text-align: right;
cursor: pointer;
}
.quota-icon {
color: #5289fa;
cursor: pointer;
}
.operate {
display: flex;
align-items: center;
flex-wrap: wrap;
.operate__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
}
}
.operate-text {
color: #5289fa;
cursor: pointer;
}
.course-start-end {
margin-left: 16px;
width: 78px;
height: 20px;
border-radius: 2px;
border: 1px solid rgba(204, 204, 204, 1);
display: flex;
align-items: center;
cursor: pointer;
white-space: nowrap;
.start-icon {
color: #3296fa;
font-size: 12px;
transform: scale(0.8);
margin: 0 5px;
}
.end-icon {
color: #00d700;
font-size: 12px;
transform: scale(0.8);
margin: 0 5px;
}
.start-end-text {
font-size: 12px;
}
}
}
.add-course-modal {
.ant-tabs-top > .ant-tabs-nav::before {
border-bottom: 0px;
}
.ant-tabs-nav-list {
margin: 0 auto;
flex:none !important
}
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
font-weight: normal;
border-bottom: 0px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 6px 12px !important;
margin: 0;
border: 0.5px solid #e8e8e8;
font-size: 14px !important;
color: #999;
&:nth-child(1) {
border-radius: 4px 0px 0px 4px;
}
&:nth-child(3) {
border-radius: 0px 4px 4px 0px;
}
}
.ant-tabs-nav .ant-tabs-tab-active {
border: 1px solid #ffb714;
color: #ffb714;
}
.ant-tabs-top .ant-tabs-ink-bar-animated:after {
height: 0;
}
.ant-modal-content tr > td{
padding:12px 8px !important;
}
}
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-13 11:48:24
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-01 11:58:46
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Table, Modal, message, Tooltip, Switch, Dropdown } from "antd";
import { PageControl } from "@/components";
import _ from "underscore";
import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass";
import { appId, shareUrl, LIVE_SHARE } from "@/domains/course-domain/constants";
import TableSelectedData from "@/components/TableSelectedData";
import "./LiveList.less";
import CourseService from "@/domains/course-domain/CourseService";
import User from "@/common/js/user";
const ENV = process.env.DEPLOY_ENV || "dev";
class VideoList extends React.Component {
constructor(props) {
super(props);
this.state = {
id: "", // 视频课ID
studentIds: [],
selectedRowKeys: [],
query: {
size: 10,
current: 1,
storeId: User.getStoreId(),
},
dataSource: [], // 视频课列表
totalCount: 0, // 视频课数据总条数
};
}
componentDidUpdate(prevProps, prevState) {
//必须写在if里面并且重新进行一次this.props !== prevProps的判断
if (
((this.props.courseName || this.props.courseType) &&
this.props.courseName !== prevProps.courseName) ||
this.props.courseType !== prevProps.courseType
) {
this.handleFetchScheduleList(this.props);
}
}
componentWillMount() {
// 获取视频课列表
this.handleFetchScheduleList();
}
// 获取视频课列表
handleFetchScheduleList = (_query = {}) => {
const query = {
...this.state.query,
..._query,
};
// 更新请求参数
this.setState({ query });
CourseService.videoSchedulePage(query).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total),
});
});
};
dealTimeDuration = (time) => {
const diff = Math.floor(time % 3600);
let hours = Math.floor(time / 3600);
let mins = Math.floor(diff / 60);
let seconds = Math.floor(time % 60);
hours = hours < 10 ? "0" + hours : hours;
mins = mins < 10 ? "0" + mins : mins;
seconds = seconds < 10 ? "0" + seconds : seconds;
return hours + ":" + mins + ":" + seconds;
};
// 请求表头
parseColumns = () => {
const columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip
title={
<div>
已加入该分类的课程不支持重复选择,因此不显示。
</div>
}
>
<i
className="icon iconfont"
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#bfbfbf",
fontSize: "14px",
}}
>
&#xe61d;
</i>
</Tooltip>
</span>
),
key: "scheduleName",
dataIndex: "scheduleName",
width: 371,
render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record;
return (
<div className="record__item">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img
className="course-cover"
src={
coverUrl ||
`${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
}
/>
{record.courseName.length > 25 ? (
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
) : (
<div className="course-name">{record.courseName}</div>
)}
</div>
);
},
},
{
title: "课程时长",
key: "videoDuration",
dataIndex: "videoDuration",
render: (text, item) => {
return <span>{text ? this.dealTimeDuration(text) : "-"}</span>;
},
},
{
title: "课程分类",
key: "categoryName",
dataIndex: "categoryName",
render: (val, record) => {
return (
<div className="record__item">
{record.categoryOneName}
{record.categoryTwoName ? `-${record.categoryTwoName}` : ""}
</div>
);
},
},
];
return columns;
};
selectLiveList = (record, selected) => {
let { selectedRowKeys } = this.state;
let _list = [];
if (selected || !_.find(selectedRowKeys, (item) => item.id == record.id)) {
_list = _.uniq(
selectedRowKeys.concat([record]),
false,
(item) => item.id
);
} else {
_list = _.reject(selectedRowKeys, (item) => item.id === record.id);
}
this.setState({ selectedRowKeys: _list });
};
render() {
const { dataSource = [], totalCount, query, selectedRowKeys } = this.state;
const { current, size } = query;
const rowSelection = {
selectedRowKeys: _.pluck(selectedRowKeys, "id"),
onSelect: this.selectLiveList,
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(
selectedRowKeys.concat(changeRows),
false,
(item) => item.id
);
} else {
_list = _.reject(selectedRowKeys, (item) =>
_.find(changeRows, (data) => data.id === item.id)
);
}
this.setState({ selectedRowKeys: _list });
},
};
return (
<div className="live-list">
<TableSelectedData
selectedNum={selectedRowKeys.length}
clearSelectedData={() => {
this.setState({
selectedRowKeys: [],
});
}}
/>
<Table
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
size="middle"
rowSelection={rowSelection}
pagination={false}
bordered
className="video-list-table"
/>
<div className="box-footer">
{totalCount > 0 && (
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.handleFetchScheduleList(_query);
}}
/>
)}
</div>
</div>
);
}
}
export default VideoList;
/*
* @Author: zhangleyuan
* @Date: 2020-11-27 15:06:31
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-09 14:22:10
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-22 13:55:24
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......
......@@ -2,7 +2,7 @@
* @Author: zhangleyuan
* @Date: 2021-02-20 16:13:39
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-16 11:27:06
* @LastEditTime: 2021-03-30 18:07:11
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -28,6 +28,7 @@ const defaultBasicData = {
operateType:'All_Operate',
percentCompleteLive:80,
percentCompleteVideo:80,
percentCompletePicture:100
}
const defaultTaskList = [];
......@@ -64,6 +65,7 @@ function AddPlan() {
operateIds,
percentCompleteLive,
percentCompleteVideo,
percentCompletePicture,
courseMediaVOS,
trainingTaskList
} = res.result;
......@@ -102,7 +104,8 @@ function AddPlan() {
instro,
operateType,
percentCompleteLive,
percentCompleteVideo
percentCompleteVideo,
percentCompletePicture
})
setHasGetDetail(true);
})
......@@ -119,7 +122,7 @@ function AddPlan() {
}
function submitInfo(){
const {planName,enableState,selectOperatorList,instro,operateType,percentCompleteLive,percentCompleteVideo,coverId,coverUrl} = basicData;
const {planName,enableState,selectOperatorList,instro,operateType,percentCompleteLive,percentCompleteVideo,percentCompletePicture,coverId,coverUrl} = basicData;
let input = /^[\s]*$/;
if(!planName || input.test(planName)){
message.warning('请输入的培训计划名称');
......@@ -137,6 +140,10 @@ function AddPlan() {
message.warning('请输入完成标准');
return;
}
if(!percentCompletePicture && percentCompletePicture !==0 ){
message.warning('请输入完成标准');
return;
}
if(taskList.length === 0){
message.warning('请输入培训计划内容');
return;
......@@ -183,6 +190,7 @@ function AddPlan() {
operateType,
percentCompleteLive,
percentCompleteVideo,
percentCompletePicture,
planName,
scheduleMediaRequests,
storeId:User.getStoreId(),
......
......@@ -77,7 +77,7 @@ class LearningData extends React.Component {
<div className="plan-info">
<div className="plan-intro">
<div className="plan-img-con">
<img src="https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"/>
<img src={coverUrl} />
</div>
<div>
<div className="plan-name">
......
......@@ -2,7 +2,7 @@
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-15 17:01:47
* @LastEditTime: 2021-03-27 11:20:27
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -210,7 +210,7 @@ class BasicInfo extends React.Component{
render(){
const { operatorModalVisible ,showSelectFileModal,visible,hasImgReady,cutImageBlob} = this.state;
const { data} = this.props;
const { planName,coverUrl,instro,enableState,operateType,selectOperatorList,percentCompleteLive,percentCompleteVideo} = data;
const { planName,coverUrl,instro,enableState,operateType,selectOperatorList,percentCompleteLive,percentCompleteVideo,percentCompletePicture} = data;
// 当前是否使用的是默认图片
const isDefaultCover = coverUrl === defaultCover;
return (
......@@ -337,6 +337,19 @@ class BasicInfo extends React.Component{
%,即视为"已完成"学习
</span>
</div>
<div className="live-standard-info">
<span className="icon iconfont">&#xe601;</span>
<span className="instro">图文课单个课程,用户学习进度达到
<Input
width="40"
value={percentCompletePicture}
onChange={(e) => { this.props.onChange('percentCompletePicture', e.target.value.replace(/\D/g,'')) }}
onBlur={(e)=>this.percentCompleteBlur(e,'percentCompletePicture')}
className="input-box"
/>
%,即视为"已完成"学习
</span>
</div>
{/* <div className="video-standard-info">
<span className="icon iconfont">&#xe864;</span>
<span>图文课单个课程,用户学习进度达到
......
......@@ -2,7 +2,7 @@
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-16 11:36:36
* @LastEditTime: 2021-03-27 14:43:17
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -25,6 +25,9 @@ const CourseType = {
},
RECORD : {
text:'录播课'
},
PICTURE:{
text:'图文课'
}
};
const courseStateShow = {
......@@ -218,6 +221,7 @@ class TrainingTask extends React.Component {
})
}
confirmSelectCourse = (selectList) =>{
console.log("selectList",selectList);
const {selectedTaskIndex}= this.state;
const { dataSource } = this.state
const newData=[...dataSource];
......
......@@ -16,6 +16,9 @@ const CourseType = {
},
RECORD : {
text:'录播课'
},
PICTURE:{
text:'图文课'
}
};
const courseStateShow = {
......
......@@ -3,6 +3,7 @@ import {Table, Modal,Input,message,Button,Tooltip} from 'antd';
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user';
import Service from '@/common/js/service';
import { Tabs } from 'antd';
import './relatedCourseModal.less';
import _ from "underscore";
......@@ -45,6 +46,7 @@ class SelectOperatorModal extends React.Component {
selectLive:[],//弹窗内已选择的直播课程
currentCourseListData:[],
currentLiveCourseListData:[], //页面中已关联的直播课程
videoDataSource:[],
videoSize:10,
videoQuery: {
......@@ -53,6 +55,16 @@ class SelectOperatorModal extends React.Component {
videoTotalCount:0,
selectVideo:[], //弹窗内已选择的视频课程
currentVideoCourseListData:[], //页面中已关联的视频课程
pictureDataSource:[],
pictureSize:10,
pictureQuery: {
current: 1,
},
pictureTotalCount:0,
selectPicture:[], //弹窗内已选择的视频课程
currentPictureCourseListData:[], //页面中已关联的视频课程
activeKey:'video',
currentTaskCourseData:this.props.data[this.props.selectedTaskIndex].courseList || []
}
......@@ -61,7 +73,7 @@ class SelectOperatorModal extends React.Component {
componentDidMount() {
this.handleFetchLiveDataList();
this.handleFetchVideoDataList();
this.handleFetchPictureDataList();
}
// 获取直播课列表
......@@ -128,6 +140,40 @@ class SelectOperatorModal extends React.Component {
});
}
// 获取图文课列表
handleFetchPictureDataList = () => {
const {pictureQuery,pictureSize} = this.state;
const { selectedTaskIndex } =this.props;
const _data = [...this.props.data];
let currentPictureCourseListData = [];
_data.map((item,index) => {
item.courseList.map((childItem,childIndex)=>{
if(childItem.courseType ==="PICTURE"){
currentPictureCourseListData.push(childItem.courseId)
}
return childItem
})
return item
});
const params ={
...pictureQuery,
size:pictureSize,
courseType:"PICTURE",
storeId:User.getStoreId(),
excludeCourseIdList:currentPictureCourseListData
}
Service.Hades('public/hades/mediaCoursePage', params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
pictureDataSource: records,
pictureTotalCount: Number(total),
currentPictureCourseListData
});
});
}
handleChangVideoCourseName = (value)=>{
const { videoQuery } = this.state;
videoQuery.courseName = value;
......@@ -136,6 +182,7 @@ class SelectOperatorModal extends React.Component {
videoQuery
})
}
handleChangLiveCourseName = (value)=>{
const { liveQuery } = this.state;
liveQuery.courseName = value;
......@@ -144,6 +191,16 @@ class SelectOperatorModal extends React.Component {
liveQuery
})
}
handleChangPictureCourseName = (value)=>{
const { pictureQuery } = this.state;
pictureQuery.courseName = value;
pictureQuery.current = 1;
this.setState({
pictureQuery
})
}
onShowLiveSizeChange = (current, size) => {
if (current == size) {
return
......@@ -162,6 +219,15 @@ class SelectOperatorModal extends React.Component {
},()=>{this.handleFetchLiveDataList()})
}
onShowPictureSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
pictureSize:size
},()=>{this.handleFetchPictureDataList()})
}
// 请求表头
parseLiveColumns = () => {
const columns = [
......@@ -280,15 +346,67 @@ class SelectOperatorModal extends React.Component {
];
return columns;
}
// 请求表头
parsePictureColumns = () => {
const columns = [
{
title: <span><span>课程信息</span><Tooltip title="仅显示未关联课程,已关联课程不支持重复选择"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip></span>,
key: 'course',
dataIndex: 'course',
width:'55%',
render: (val, record) => {
const { coverUrl } = record;
return (
<div className="course-info">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img className="course-cover" src={coverUrl || 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png'}/>
<div className="course-name">{record.courseName}</div>
</div>
)
}
},
{
title: '更新时间',
key: 'updated',
dataIndex: 'updated',
width:'25%',
render: (val, record) => {
return (
<span className="course-status">{formatDate('YYYY-MM-DD',record.updated)}</span>
)
}
},
{
title: '店铺展示',
key: 'shelfState',
dataIndex: 'shelfState',
width:'20%',
render: (val, record) => {
return (
<span>
{record.shelfState==="YES"?
<span>开启</span>
:
<span>关闭</span>
}
</span>
)
}
}
];
return columns;
}
selectLiveList = (record,selected) =>{
const {selectVideo,currentTaskCourseData,selectLive} = this.state;
const {selectVideo,currentTaskCourseData,selectLive,selectPicture} = this.state;
let _list = [];
if (selected || !_.find(selectLive, (item) => item.liveCourseId == record.liveCourseId)) {
_list = _.uniq(selectLive.concat([record]), false, (item) => item.liveCourseId);
} else {
_list = _.reject(selectLive, (item) => item.liveCourseId === record.liveCourseId);
}
if(_list.length + currentTaskCourseData.length + selectVideo.length> 20){
if(_list.length + currentTaskCourseData.length + selectVideo.length + selectPicture.length > 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
......@@ -296,23 +414,40 @@ class SelectOperatorModal extends React.Component {
}
selectVideoList = (record,selected) =>{
const {selectVideo,currentTaskCourseData,selectLive} = this.state;
const {selectVideo,currentTaskCourseData,selectLive,selectPicture} = this.state;
let _list = [];
if (selected || !_.find(selectVideo, (item) => item.id == record.id)) {
_list = _.uniq(selectVideo.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectVideo, (item) => item.id === record.id);
}
if(_list.length + currentTaskCourseData.length + selectLive.length> 20){
if(_list.length + currentTaskCourseData.length + selectLive.length + selectPicture.length > 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({selectVideo:_list});
}
selectPictureList = (record,selected) =>{
const {selectVideo,currentTaskCourseData,selectLive,selectPicture} = this.state;
let _list = [];
if (selected || !_.find(selectPicture, (item) => item.id == record.id)) {
_list = _.uniq(selectPicture.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectPicture, (item) => item.id === record.id);
}
if(_list.length + currentTaskCourseData.length + selectLive.length + selectVideo.length > 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({selectPicture:_list});
}
clearSelectCourse = ()=>{
this.setState({
selectLive:[],
selectVideo:[]
selectVideo:[],
selectPicture:[]
})
}
handleSelectVideo = (selectVideo)=>{
......@@ -334,10 +469,57 @@ class SelectOperatorModal extends React.Component {
_item.courseState = item.courseState;
return _item;
})
}
}
handleSelectPicture = (selectPicture)=>{
return selectPicture.map((item,index)=>{
let _item = {};
_item.courseId = item.id;
_item.courseType = "PICTURE";
_item.courseName = item.courseName;
return _item;
})
}
renderFooter = ()=>{
const { activeKey } = this.state;
let href = '';
switch (activeKey){
case 'live':
href = <a target='_blank' className="link-create-course" href={window.location.origin + window.location.pathname + '#/create-live-course?type=add'} onClick={this.props.onClose}>没有找到需要的直播课?<span>去创建</span></a>
break;
case 'video':
href = <a target='_blank' className="link-create-course" href={window.location.origin + window.location.pathname + '#/create-video-course?type=add'} onClick={this.props.onClose}>没有找到需要的视频课?<span>去创建</span></a>
break;
case 'picture':
href = <a target='_blank' className="link-create-course" href={window.location.origin + window.location.pathname + '#/create-graphics-course?type=add'} onClick={this.props.onClose}>没有找到需要的图文课?<span>去创建</span></a>
break;
}
return href;
}
render() {
const { visible } = this.props;
const { liveDataSource,liveSize,liveQuery,liveTotalCount,selectLive,videoDataSource,videoSize,videoQuery,videoTotalCount,selectVideo,currentTaskCourseData,activeKey} = this.state;
const {
liveDataSource,
liveSize,
liveQuery,
liveTotalCount,
selectLive,
videoDataSource,
videoSize,
videoQuery,
videoTotalCount,
selectVideo,
currentTaskCourseData,
activeKey,
selectPicture,
pictureDataSource,
pictureSize,
pictureQuery,
pictureTotalCount,
} = this.state;
return (
<Modal
......@@ -348,13 +530,9 @@ class SelectOperatorModal extends React.Component {
className="related-course-modal"
closable={true}
width={800}
onOk={() => this.props.onSelect([...this.handleSelectVideo(selectVideo),...this.handleSelectLive(selectLive)]) }
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
footer={[
activeKey ==='live'?
<a target='_blank' className="link-create-course" href={window.location.origin + window.location.pathname + '#/create-live-course?type=add'} onClick={this.props.onClose}>没有找到需要的直播课?<span>去创建</span></a>
:
<a target='_blank' className="link-create-course" href={window.location.origin + window.location.pathname + '#/create-video-course?type=add'} onClick={this.props.onClose}>没有找到需要的视频课?<span>去创建</span></a>
this.renderFooter()
,
<Button
onClick={() => {
......@@ -363,7 +541,7 @@ class SelectOperatorModal extends React.Component {
>
取消
</Button>,
<Button type="primary" onClick={() => this.props.onSelect([...this.handleSelectVideo(selectVideo),...this.handleSelectLive(selectLive)]) }>
<Button type="primary" onClick={() => this.props.onSelect([...this.handleSelectVideo(selectVideo),...this.handleSelectLive(selectLive),...this.handleSelectPicture(selectPicture)]) }>
确定
</Button>
]}
......@@ -379,7 +557,7 @@ class SelectOperatorModal extends React.Component {
<div className="select-box">
<div>
<span className="icon iconfont tip-icon">&#xe61d;</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length}</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length + selectPicture.length }</span>
</div>
<div>
<span className="clear-btn" onClick={this.clearSelectCourse}>清空</span>
......@@ -407,10 +585,9 @@ class SelectOperatorModal extends React.Component {
} else {
_list = _.reject(selectVideo, (item) => _.find(changeRows, (data) => data.id === item.id));
}
if(_list.length + currentTaskCourseData.length + selectLive.length> 20){
if(_list.length + currentTaskCourseData.length + selectLive.length + selectPicture.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
console.log('1111');
const extraLength = (_list.length + currentTaskCourseData.length + selectLive.length) -20;
const extraLength = (_list.length + currentTaskCourseData.length + selectLive.length + selectPicture.length) -20;
_list.splice(_list.length - extraLength,extraLength);
}
this.setState({selectVideo:_list});
......@@ -445,7 +622,7 @@ class SelectOperatorModal extends React.Component {
<div className="select-box">
<div>
<span className="icon iconfont tip-icon">&#xe61d;</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length}</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length + selectPicture.length}</span>
</div>
<div>
<span className="clear-btn" onClick={this.clearSelectCourse}>清空</span>
......@@ -473,11 +650,10 @@ class SelectOperatorModal extends React.Component {
} else {
_list = _.reject(selectLive, (item) => _.find(changeRows, (data) => data.liveCourseId === item.liveCourseId));
}
if(_list.length + currentTaskCourseData.length + selectVideo.length> 20){
if(_list.length + currentTaskCourseData.length + selectVideo.length + selectPicture.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength = (_list.length + currentTaskCourseData.length + selectVideo.length) -20;
const extraLength = (_list.length + currentTaskCourseData.length + selectVideo.length + selectPicture.length) -20;
_list.splice(_list.length - extraLength,extraLength);
console.log('_list',_list);
}
this.setState({selectLive:_list});
......@@ -503,6 +679,72 @@ class SelectOperatorModal extends React.Component {
}
</div>
</TabPane>
<TabPane tab="图文课" key="picture">
<div className="search-container">
<Search enterButton={<span className="icon iconfont">&#xe832;</span>} placeholder="搜索课程名称" style={{ width: 200 }} onChange={(e) => { this.handleChangPictureCourseName(e.target.value)}} onSearch={ () => { this.handleFetchPictureDataList()}} />
</div>
<div className="select-area">
<div className="select-box">
<div>
<span className="icon iconfont tip-icon">&#xe61d;</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length + selectPicture.length }</span>
</div>
<div>
<span className="clear-btn" onClick={this.clearSelectCourse}>清空</span>
</div>
</div>
<div className="related-box">该任务已关联{currentTaskCourseData.length}个课程,可继续选择{20- currentTaskCourseData.length }</div>
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={pictureDataSource}
columns={this.parsePictureColumns()}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectPicture, 'id'),
onSelect: (record, selected) => {
this.selectPictureList(record, selected)
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectPicture.concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectPicture, (item) => _.find(changeRows, (data) => data.id === item.id));
}
if(_list.length + currentTaskCourseData.length + selectVideo.length + selectLive.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength = (_list.length + currentTaskCourseData.length + selectVideo.length + selectLive.length) -20;
_list.splice(_list.length - extraLength,extraLength);
}
this.setState({selectPicture:_list});
},
}}
/>
{pictureDataSource.length >0 &&
<div className="box-footer">
<PageControl
current={pictureQuery.current - 1}
pageSize={pictureSize}
size="small"
total={pictureTotalCount}
toPage={(page) => {
const _query = {...pictureQuery, current: page + 1};
this.setState({
pictureQuery:_query
},()=>{ this.handleFetchPictureDataList()})
}}
onShowSizeChange={this.onShowPictureSizeChange}
/>
</div>
}
</div>
</TabPane>
</Tabs>
</div>
</Modal>
......
......@@ -60,6 +60,29 @@ class FolderList extends React.Component {
}
}
// 埋点
handleDataDot = (folderFormat) => {
switch (folderFormat) {
case 'PDF':
window.WEBTRACING('resource_disk_file_preview_pdf', '资料云盘_点击预览_pdf');
break;
case 'WORD':
case 'DOCX':
case 'DOC':
window.WEBTRACING('resource_disk_file_preview_word', '资料云盘_点击预览_word');
break;
case 'EXCEL':
window.WEBTRACING('resource_disk_file_preview_excel', '资料云盘_点击预览_excel');
break;
case 'PPT':
case 'PPTX':
window.WEBTRACING('resource_disk_file_preview_ppt', '资料云盘_点击预览_ppt');
break;
default:
break;
}
}
// 预览文件
handleScanFile = (folder) => {
const { folderFormat, folderSize, ossUrl } = folder;
......@@ -119,6 +142,9 @@ class FolderList extends React.Component {
this.setState({ scanFileModal });
break;
}
// 预览文件埋点
this.handleDataDot(folderFormat);
}
// 选择文件夹
......
......@@ -73,6 +73,29 @@ class FolderList extends React.Component {
}
}
// 埋点
handleDataDot = (folderFormat) => {
switch (folderFormat) {
case 'PDF':
window.WEBTRACING('resource_disk_file_preview_pdf', '资料云盘_点击预览_pdf');
break;
case 'WORD':
case 'DOCX':
case 'DOC':
window.WEBTRACING('resource_disk_file_preview_word', '资料云盘_点击预览_word');
break;
case 'EXCEL':
window.WEBTRACING('resource_disk_file_preview_excel', '资料云盘_点击预览_excel');
break;
case 'PPT':
case 'PPTX':
window.WEBTRACING('resource_disk_file_preview_ppt', '资料云盘_点击预览_ppt');
break;
default:
break;
}
}
// 预览文件
handleScanFile = (folder) => {
const { folderFormat, folderSize, ossUrl } = folder;
......@@ -132,6 +155,9 @@ class FolderList extends React.Component {
this.setState({ scanFileModal });
break;
}
// 预览文件埋点
this.handleDataDot(folderFormat);
}
// 选择文件夹
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2019-07-10 10:30:49
* @LastEditors: wufan
* @LastEditTime: 2021-03-11 17:22:01
* @LastEditTime: 2021-03-26 16:13:48
* @Description:
*/
import React, { useContext, useEffect, useState } from 'react';
......@@ -17,6 +17,7 @@ import BaseService from "@/domains/basic-domain/baseService";
import { XMContext } from '@/store/context';
import { setStoreGroupPermission, setStorePermission, setStoreGroupList, setStoreList } from '@/store/actions/index';
import Bus from '@/core/tbus';
declare var window: any;
const App: React.FC = (props: any) => {
......@@ -29,9 +30,11 @@ const App: React.FC = (props: any) => {
useEffect(() => {
getStoreAndUserInfo();
// window.RCHistory.push({
// pathname: `/switch-route`,
// })
if (window.location.hash === "#/") {
window.RCHistory.replace({
pathname: '/home',
})
}
}, [])
async function getStoreAndUserInfo() {
......
......@@ -377,7 +377,7 @@ class StoreH5Decoration extends React.Component {
<div className="banner-setting">
<div className="title">banner设置</div>
<div className="tip">
图片支持bmp、jpeg、jpg、png、gif格式,最大5M,最多可添加5张,拖动可排序。建议尺寸750x252像素
图片支持bmp、jpeg、jpg、png、gif格式,最大5M,最多可添加5张,拖动可排序。建议尺寸750*252px
</div>
</div>
<Button
......
......@@ -375,7 +375,7 @@ class StoreWebDecoration extends React.Component {
<div className="banner-setting">
<div className="title">banner设置</div>
<div className="tip">
图片支持bmp、jpeg、jpg、png、gif格式,最大5M,最多可添加5张,拖动可排序。建议尺寸1232*212像素
图片支持bmp、jpeg、jpg、png、gif格式,最大5M,最多可添加5张,拖动可排序。建议尺寸1232*212px
</div>
</div>
<Button
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-23 18:28:50
* @LastEditors: yuananting
* @LastEditTime: 2021-04-07 10:09:18
* @LastEditTime: 2021-04-15 21:43:35
* @Description: 助学工具-课程分类
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......
......@@ -2,21 +2,21 @@
* @Author: 吴文洁
* @Date: 2020-04-29 10:26:32
* @LastEditors: yuananting
* @LastEditTime: 2021-04-07 10:13:06
* @LastEditTime: 2021-04-15 21:45:42
* @Description: 内容线路由配置
*/
import Home from '@/modules/home/Home';
import EmployeesManagePage from '@/modules/store-manage/EmployeesManagePage';
import personalInfoPage from '@/modules/personalInfo';
import UserManagePage from '@/modules/store-manage/UserManagePage';
import StoreDecorationPage from '@/modules/store-manage/StoreDecorationPage';
import CourseCatalogPage from '@/modules/store-manage/CourseCatalogPage';
import LiveCoursePage from '@/modules/course-manage/LiveCoursePage';
import AddLivePage from '@/modules/course-manage/AddLive'
import VideoCoursePage from '@/modules/course-manage/video-course'
import GraphicsCoursePage from '@/modules/course-manage/graphics-course'
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse'
import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGraphicsCourse'
import Home from "@/modules/home/Home";
import EmployeesManagePage from "@/modules/store-manage/EmployeesManagePage";
import personalInfoPage from "@/modules/personalInfo";
import UserManagePage from "@/modules/store-manage/UserManagePage";
import StoreDecorationPage from "@/modules/store-manage/StoreDecorationPage";
import CourseCatalogPage from "@/modules/store-manage/CourseCatalogPage";
import LiveCoursePage from "@/modules/course-manage/LiveCoursePage";
import AddLivePage from "@/modules/course-manage/AddLive";
import VideoCoursePage from "@/modules/course-manage/video-course";
import GraphicsCoursePage from "@/modules/course-manage/graphics-course";
import AddVideoCoursePage from "@/modules/course-manage/video-course/AddVideoCourse";
import AddGraphicsCoursePage from "@/modules/course-manage/graphics-course/AddGraphicsCourse";
// import DataList from '@/modules/course-manage/DataList/DataList';
// import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk';
......@@ -27,76 +27,81 @@ import LearningDataPage from '@/modules/plan-manage/LearningData';
import StoreInfoPage from '@/modules/store-manage/StoreInfo';
import CourseCategoryManage from '@/modules/teach-tool/components/CourseCategoryManage';
import QuestionManageIndex from '@/modules/teach-tool/question-manage/Index';
import OperateQuestion from "@/modules/teach-tool/question-manage/OperateQuestion";
import PaperManageIndex from '@/modules/teach-tool/paper-manage/Index';
import OperatePaper from "@/modules/teach-tool/paper-manage/OperatePaper";
import ExaminationManagerIndex from '@/modules/teach-tool/examination-manager/Index';
import KnowledgeBase from "@/modules/knowledge-base/index";
const mainRoutes = [
{
path: '/home',
path: "/home",
component: Home,
name: '中心首页'
name: "中心首页",
},
{
path: '/employees-manage',
path: "/employees-manage",
component: EmployeesManagePage,
name: '员工管理'
name: "员工管理",
},
{
path: '/personal-info',
path: "/personal-info",
component: personalInfoPage,
name: '个人信息'
name: "个人信息",
},
{
path: '/user-manage',
path: "/user-manage",
component: UserManagePage,
name: '用户管理'
name: "用户管理",
},
{
path: '/store-decoration',
path: "/store-decoration",
component: StoreDecorationPage,
name: '店铺装修'
name: "店铺装修",
},
// {
// path: "/course-catalog",
// component: CourseCatalogPage,
// name: "课程分类",
// },
{
path: '/course-catalog',
component:CourseCatalogPage,
name: '课程分类'
path: "/live-course",
component: LiveCoursePage,
name: "直播课",
},
{
path: '/live-course',
component:LiveCoursePage,
name: '直播课'
path: "/video-course",
component: VideoCoursePage,
name: "视频课",
},
{
path: '/video-course',
component:VideoCoursePage,
name: '视频课'
path: "/graphics-course",
component: GraphicsCoursePage,
name: "图文课",
},
{
path: '/graphics-course',
component: GraphicsCoursePage,
name: '图文课'
path: "/create-live-course",
component: AddLivePage,
name: "创建直播课",
},
{
path: '/create-live-course',
component:AddLivePage,
name: '创建直播课'
path: "/create-video-course",
component: AddVideoCoursePage,
name: "创建视频课",
},
{
path: '/create-video-course',
component:AddVideoCoursePage,
name: '创建视频课'
path: "/knowledge-base",
// component:ResourceDisk,
component: KnowledgeBase,
name: "知识库",
},
{
path: '/create-graphics-course',
component:AddGraphicsCoursePage,
name: '创建图文课'
path: "/create-graphics-course",
component: AddGraphicsCoursePage,
name: "创建图文课",
},
{
path: '/resource-disk',
component:ResourceDisk,
name: '资料云盘'
path: "/resource-disk",
component: ResourceDisk,
name: "资料云盘",
},
{
path: '/question-manage-index',
......@@ -119,31 +124,30 @@ const mainRoutes = [
name: '分类管理'
},
{
path: '/switch-route',
path: "/switch-route",
component: SwitchRoute,
name: '登录后跳转承载页'
name: "登录后跳转承载页",
},
{
path:'/plan',
path: "/plan",
component: PlanPage,
name: '培训计划'
name: "培训计划",
},
{
path: '/create-plan',
component:AddPlanPage,
name: '创建视频课'
path: "/create-plan",
component: AddPlanPage,
name: "创建视频课",
},
{
path: '/store-info',
component:StoreInfoPage,
name: '店铺信息'
path: "/store-info",
component: StoreInfoPage,
name: "店铺信息",
},
{
path: '/learning-data',
component:LearningDataPage,
name: '学习数据'
}
]
path: "/learning-data",
component: LearningDataPage,
name: "学习数据",
},
];
export default mainRoutes;
......@@ -36,6 +36,12 @@ export const menuList: any = [
]
},
{
groupName: "知识库",
groupCode: "CloudKnowledge",
icon: '&#xe840;',
link: '/knowledge-base'
},
{
groupName: "资料云盘",
groupCode: "CloudDisk",
icon: '&#xe83b;',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment