Commit b427be83 by wufan

fix:解决冲突

parents f17356af 8dd6143a
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-31 09:34:25 * @Date: 2020-08-31 09:34:25
* @LastEditors: Please set LastEditors * @LastEditors: yuananting
* @LastEditTime: 2021-07-20 17:21:16 * @LastEditTime: 2021-08-17 19:20:33
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import Storage from './storage'; import Storage from './storage';
import { PREFIX, USER_PREFIX } from '@/domains/basic-domain/constants'; import { PREFIX, USER_PREFIX } from '@/domains/basic-domain/constants';
declare var window:any; declare var window: any;
class User { class User {
getExpirationTime() { getExpirationTime() {
return Storage.get(`${PREFIX}_expiration_time`) return Storage.get(`${PREFIX}_expiration_time`);
} }
getVersion() { getVersion() {
return Storage.getObj(`${PREFIX}_version`) return Storage.getObj(`${PREFIX}_version`);
} }
getStoreId(){ getStoreId() {
return window.currentStoreUserInfo.storeId || Storage.get(`${PREFIX}_storeId`) return window.currentStoreUserInfo.storeId || Storage.get(`${PREFIX}_storeId`);
} }
getEnterpriseId() { getEnterpriseId() {
...@@ -40,18 +39,22 @@ class User { ...@@ -40,18 +39,22 @@ class User {
return window.currentStoreUserInfo.storeUserId || Storage.get(`${PREFIX}_storeUserId`); return window.currentStoreUserInfo.storeUserId || Storage.get(`${PREFIX}_storeUserId`);
} }
getStoreUserName() {
return Storage.get(`${PREFIX}_storeUserName`);
}
getCustomerId() { getCustomerId() {
return Storage.get(`${PREFIX}_customerId`); return Storage.get(`${PREFIX}_customerId`);
} }
getUserId() { getUserId() {
return window.currentStoreUserInfo.userId || Storage.get(`${PREFIX}_userId`); return window.currentStoreUserInfo.userId || Storage.get(`${PREFIX}_userId`);
} }
getAvatar(){ getAvatar() {
return Storage.get(`${PREFIX}_avatar`); return Storage.get(`${PREFIX}_avatar`);
} }
getUserRole() { getUserRole() {
return Storage.get(`${PREFIX}_userRole`); return Storage.get(`${PREFIX}_userRole`);
} }
...@@ -64,16 +67,16 @@ class User { ...@@ -64,16 +67,16 @@ class User {
return Storage.get(`${PREFIX}_isAdmin`); return Storage.get(`${PREFIX}_isAdmin`);
} }
setExpirationTime(value:number) { setExpirationTime(value: number) {
return Storage.set(`${PREFIX}_expiration_time`,value) return Storage.set(`${PREFIX}_expiration_time`, value);
} }
setVersion(value:any) { setVersion(value: any) {
return Storage.setObj(`${PREFIX}_version`,value) return Storage.setObj(`${PREFIX}_version`, value);
} }
setStoreId(value:any){ setStoreId(value: any) {
return Storage.set(`${PREFIX}_storeId`,value) return Storage.set(`${PREFIX}_storeId`, value);
} }
setEnterpriseId(value: any) { setEnterpriseId(value: any) {
...@@ -92,6 +95,10 @@ class User { ...@@ -92,6 +95,10 @@ class User {
return Storage.set(`${PREFIX}_storeUserId`, value); return Storage.set(`${PREFIX}_storeUserId`, value);
} }
setStoreUserName(value: any) {
return Storage.set(`${PREFIX}_storeUserName`, value);
}
setCustomerId(value: any) { setCustomerId(value: any) {
return Storage.set(`${PREFIX}_customerId`, value); return Storage.set(`${PREFIX}_customerId`, value);
} }
......
@font-face { @font-face {
font-family: 'iconfont'; /* Project id 2223403 */ font-family: 'iconfont'; /* Project id 2223403 */
src: url('//at.alicdn.com/t/font_2223403_7261tsts1dc.woff2?t=1628475376853') format('woff2'), src: url('//at.alicdn.com/t/font_2223403_0b87tvtysw45.woff2?t=1629025918841') format('woff2'),
url('//at.alicdn.com/t/font_2223403_7261tsts1dc.woff?t=1628475376853') format('woff'), url('//at.alicdn.com/t/font_2223403_0b87tvtysw45.woff?t=1629025918841') format('woff'),
url('//at.alicdn.com/t/font_2223403_7261tsts1dc.ttf?t=1628475376853') format('truetype'); url('//at.alicdn.com/t/font_2223403_0b87tvtysw45.ttf?t=1629025918841') format('truetype');
} }
.iconfont { .iconfont {
font-family: 'iconfont' !important; font-family: 'iconfont' !important;
......
import React, { useState, useEffect } from 'react';
export default function RichText(props) {
const [text, setText] = useState('')
useEffect(() => {
console.log(props.url,'props.urlprops.url')
if (props.url) {
fetch(props.url, { method: 'GET' }).then((response) => {
return response.text();
}).then((res) => {
setText(res)
})
}
}, [props.url])
return <div className="text" style={{ wordBreak: 'break-all' }} dangerouslySetInnerHTML={{ __html: text }}></div>
}
\ No newline at end of file
...@@ -2,8 +2,6 @@ import React, { useRef, useLayoutEffect } from 'react' ...@@ -2,8 +2,6 @@ import React, { useRef, useLayoutEffect } from 'react'
export default function WWOpenDataCom({ type, openid }) { export default function WWOpenDataCom({ type, openid }) {
const ref = useRef(null) const ref = useRef(null)
useLayoutEffect(() => { useLayoutEffect(() => {
console.log('1111');
console.log(WWOpenData);
WWOpenData && WWOpenData.bind(ref.current) WWOpenData && WWOpenData.bind(ref.current)
}) })
return <ww-open-data ref={ref} type={type} openid={openid} /> return <ww-open-data ref={ref} type={type} openid={openid} />
......
...@@ -2,10 +2,14 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,10 +2,14 @@ import React, { useEffect, useState } from 'react';
import { Empty, ConfigProvider, Table } from 'antd'; import { Empty, ConfigProvider, Table } from 'antd';
import Lottie from 'react-lottie'; import Lottie from 'react-lottie';
import * as nodata from '../modules/lottie/nodata/data.json'; import * as nodata from '../modules/lottie/nodata/data.json';
import college from '@/common/lottie/college';
function XMTable(props) { function XMTable(props) {
const [empty, setEmpty] = useState(props.renderEmpty || {}); const [empty, setEmpty] = useState(props.renderEmpty || {});
const [data, setData] = useState({}); const [data, setData] = useState({});
const imgType = {
college
}
useEffect(() => { useEffect(() => {
setEmpty(props.renderEmpty || {}) setEmpty(props.renderEmpty || {})
...@@ -17,7 +21,7 @@ function XMTable(props) { ...@@ -17,7 +21,7 @@ function XMTable(props) {
const defaultOptions = { const defaultOptions = {
loop: true, loop: true,
autoplay: true, autoplay: true,
animationData: empty.image || nodata, animationData: empty.image || imgType[props?.renderEmpty?.type] || nodata,
rendererSettings: { rendererSettings: {
preserveAspectRatio: 'xMidYMid slice' preserveAspectRatio: 'xMidYMid slice'
} }
...@@ -25,14 +29,14 @@ function XMTable(props) { ...@@ -25,14 +29,14 @@ function XMTable(props) {
return ( return (
<Empty <Empty
image={<div style={{ marginTop: 24 }}> image={<div style={{ marginTop: 24 }}>
<Lottie <Lottie
options={defaultOptions} options={defaultOptions}
height={150} height={150}
width={150} width={150}
isStopped={false} isStopped={false}
isPaused={false} isPaused={false}
/> />
</div>} </div>}
imageStyle={{ imageStyle={{
height: 150, height: 150,
}} }}
......
...@@ -514,6 +514,7 @@ mr0 { ...@@ -514,6 +514,7 @@ mr0 {
// 气泡 // 气泡
.ant-tooltip { .ant-tooltip {
max-width: 280px !important;
.ant-tooltip-content { .ant-tooltip-content {
.ant-tooltip-inner { .ant-tooltip-inner {
padding: 6px 12px !important; padding: 6px 12px !important;
......
...@@ -1706,22 +1706,40 @@ input:focus { ...@@ -1706,22 +1706,40 @@ input:focus {
background: transparent !important; background: transparent !important;
} }
.xm_search_item {
padding-right: 24px;
width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
box-sizing: border-box;
.label{
margin-right: 8px;
flex-shrink: 0;
// height: 36px;
// line-height: 36px;
}
.search{
flex: 1;
}
.createQWCourse{ .createQWCourse{
.footer { .footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
height: 58px; height: 58px;
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
padding-right: 252px; padding-right: 252px;
background: #fff; background: #fff;
border-top: 1px solid #E8E8E8; border-top: 1px solid #E8E8E8;
z-index: 999; z-index: 999;
.ant-btn { .ant-btn {
margin-left: 10px; margin-left: 10px;
}
} }
} }
} }
\ No newline at end of file
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
z-index: 102; z-index: 102;
overflow: auto; overflow: auto;
margin: 0 16px; margin: 0 16px;
min-height: auto;
.box { .box {
&:first-child { &:first-child {
margin-bottom: 8px; margin-bottom: 8px;
......
/*
* @Author: yuananting
* @Date: 2021-08-06 17:35:35
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 20:52:16
* @Description: 任务中心接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from '@/common/js/service';
export function getTrainingTaskPage(params: object) {
return Service.Hades('public/hades/getTrainingTaskPage', params);
}
export function getStoreTaskNum(params: object) {
return Service.Hades('public/hades/getStoreTaskNum', params);
}
export function createTrainingExam(params: object) {
return Service.Hades('public/hades/createTrainingExam', params);
}
export function createTrainingTask(params: object) {
return Service.Hades('public/hades/createTrainingTask', params);
}
export function updateIssueStateTraining(params: object) {
return Service.Hades('public/hades/updateIssueStateTraining', params);
}
export function deleteTrainingTask(params: object) {
return Service.Hades('public/hades/deleteTrainingTask', params);
}
export function getTrainingTaskDetail(params: object) {
return Service.Hades('public/hades/getTrainingTaskDetail', params);
}
export function updateTrainingTask(params: object) {
return Service.Hades('public/hades/updateTrainingTask', params);
}
export function getTaskCustomerDetail(params: object) {
return Service.Hades('public/hades/getTaskCustomerDetail', params);
}
export function updateTrainingTaskAssign(params: object) {
return Service.Hades('public/hades/updateTrainingTaskAssign', params);
}
export function getTrainingCourseAutoCancel(params: object) {
return Service.Hades('public/hades/getTrainingCourseAutoCancel', params);
}
export function delTaskCancelContent(params: object) {
return Service.Hades('public/hades/delTaskCancelContent', params);
}
export function getStoreTaskAll(params: object) {
return Service.Hades('public/hades/getStoreTaskAll', params);
}
export function relatedCourseToTask(params: object) {
return Service.Hades('public/hades/relatedCourseToTask', params);
}
/* /*
* @Author: 陈剑宇 * @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01 * @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-08-11 22:52:04 * @LastEditTime: 2021-09-03 10:28:14
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @Description: * @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts * @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
*/ */
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-20 09:21:40 * @Date: 2020-08-20 09:21:40
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-08-11 22:50:48 * @LastEditTime: 2021-09-03 10:28:17
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
......
/*
* @Author: yuananting
* @Date: 2021-08-06 17:32:41
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 20:56:36
* @Description: 任务中心-培训任务接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import {
getTrainingTaskPage,
getStoreTaskNum,
createTrainingExam,
createTrainingTask,
updateIssueStateTraining,
deleteTrainingTask,
getTrainingTaskDetail,
updateTrainingTask,
getTaskCustomerDetail,
updateTrainingTaskAssign,
getTrainingCourseAutoCancel,
delTaskCancelContent,
getStoreTaskAll,
relatedCourseToTask,
} from '@/data-source/taskCenter/request-apis';
export default class TaskCenterService {
// 获取培训任务列表
static getTrainingTaskPage(params: any) {
return getTrainingTaskPage(params);
}
// 获取学院任务数量
static getStoreTaskNum(params: any) {
return getStoreTaskNum(params);
}
// 培训任务创建考试
static createTrainingExam(params: any) {
return createTrainingExam(params);
}
// 企培创建培训任务
static createTrainingTask(params: any) {
return createTrainingTask(params);
}
// 发布/取消发布培训任务
static updateIssueStateTraining(params: any) {
return updateIssueStateTraining(params);
}
// 删除培训任务
static deleteTrainingTask(params: any) {
return deleteTrainingTask(params);
}
// 获取培训任务详情
static getTrainingTaskDetail(params: any) {
return getTrainingTaskDetail(params);
}
// 修改培训任务
static updateTrainingTask(params: any) {
return updateTrainingTask(params);
}
// 获取单个任务单个学员的学习进度详情
static getTaskCustomerDetail(params: any) {
return getTaskCustomerDetail(params);
}
// 修改培训任务的指派信息
static updateTrainingTaskAssign(params: any) {
return updateTrainingTaskAssign(params);
}
// 获取培训任务失效的课程
static getTrainingCourseAutoCancel(params: any) {
return getTrainingCourseAutoCancel(params);
}
// 删除失效课程
static delTaskCancelContent(params: any) {
return delTaskCancelContent(params);
}
// 获取培训任务学习基本信息
static getStoreTaskAll(params: any) {
return getStoreTaskAll(params);
}
// 课程关联培训任务
static relatedCourseToTask(params: any) {
return relatedCourseToTask(params);
}
}
<!-- <!--
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-24 12:20:57 * @Date: 2020-08-24 12:20:57
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-05-26 16:26:17 * @LastEditTime: 2021-08-15 19:12:48
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
--> -->
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 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="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_oqqm4z9s35j.css"> <link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_0b87tvtysw45.css">
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
......
/*
* @Author: wufan
* @Date: 2021-09-03 10:23:20
* @LastEditors: wufan
* @LastEditTime: 2021-09-03 10:24:30
* @Description: Description
* @@Copyrigh: © 2021 杭州杰竞科技有限公司 版权所有
*/
declare module 'jquery' declare module 'jquery'
declare module 'cropper' declare module 'cropper'
declare module 'ExamShareModal' declare module 'ExamShareModal'
declare module 'college'
declare module '@/common/lottie/college'
declare module 'routeHooks' declare module 'routeHooks'
// declare var this: any // declare var this: any
\ No newline at end of file
<!-- <!--
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-24 12:20:57 * @Date: 2020-08-24 12:20:57
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-08-11 20:25:11 * @LastEditTime: 2021-09-03 10:26:25
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
--> -->
...@@ -25,14 +25,14 @@ ...@@ -25,14 +25,14 @@
<!-- <link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png" /> --> <!-- <link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png" /> -->
<!-- <link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/WGWCtxiGzE.png"> --> <!-- <link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/WGWCtxiGzE.png"> -->
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 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="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_7261tsts1dc.css" /> <link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_7261tsts1dc.css" />
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML. Only files inside the `public` folder can be referenced from the HTML.
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
</script> </script>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<!-- <!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.
...@@ -73,5 +73,5 @@ ...@@ -73,5 +73,5 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
</body> </body>
</html> </html>
...@@ -5,21 +5,21 @@ ...@@ -5,21 +5,21 @@
* @Last Modified time: 2021-04-10 14:36:43 * @Last Modified time: 2021-04-10 14:36:43
* 学院管理-员工管理 * 学院管理-员工管理
*/ */
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { withRouter } from "react-router-dom"; import { withRouter } from 'react-router-dom';
import _ from "underscore"; import _ from 'underscore';
import { CheckBox, PageControl } from "@/components"; import { CheckBox, PageControl } from '@/components';
import { Button, Table, Modal, message, Input,Tooltip} from "antd"; import { Button, Table, Modal, message, Input, Tooltip } from 'antd';
import { XMTable } from '@/components'; import { XMTable } from '@/components';
import college from '@/common/lottie/college.json'; import college from '@/common/lottie/college.json';
import StoreService from "@/domains/store-domain/storeService"; import StoreService from '@/domains/store-domain/storeService';
import EmployeeAddOrEditModal from "../store-manage/EmployeeAddOrEditModal"; import EmployeeAddOrEditModal from '../store-manage/EmployeeAddOrEditModal';
import User from "@/common/js/user"; import User from '@/common/js/user';
import LimitTip from "./LimitTip"; import LimitTip from './LimitTip';
import WWOpenDataCom from '@/components/WWOpenDataCom'; import WWOpenDataCom from '@/components/WWOpenDataCom';
import "./EmployeeManage.less"; import './EmployeeManage.less';
import NewChooseMembersModal from "./modal/NewChooseMembersModal"; import NewChooseMembersModal from './modal/NewChooseMembersModal';
const { confirm } = Modal; const { confirm } = Modal;
const { Search } = Input; const { Search } = Input;
...@@ -33,7 +33,7 @@ interface RecordTypes { ...@@ -33,7 +33,7 @@ interface RecordTypes {
phone: string; phone: string;
avatar?: string; avatar?: string;
weChatAccount?: string; weChatAccount?: string;
depNameList:any depNameList: any;
} }
interface RoleItemType { interface RoleItemType {
...@@ -58,7 +58,7 @@ interface ChoosedItemType { ...@@ -58,7 +58,7 @@ interface ChoosedItemType {
avatar?: string; avatar?: string;
storeUserId?: string; storeUserId?: string;
weChatAccount?: string; weChatAccount?: string;
depNameList?:any; depNameList?: any;
} }
function EmployeeManage() { function EmployeeManage() {
...@@ -66,25 +66,25 @@ function EmployeeManage() { ...@@ -66,25 +66,25 @@ function EmployeeManage() {
const [query, setQuery] = useState<QueryType>({ const [query, setQuery] = useState<QueryType>({
current: 0, current: 0,
size: 10, size: 10,
nickName: "", nickName: '',
phone: "", phone: '',
roleCodes: [], roleCodes: [],
}); });
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [realTotal, setRealTotal] = useState(0) const [realTotal, setRealTotal] = useState(0);
const [model, setModel] = useState<React.ReactNode>(null); const [model, setModel] = useState<React.ReactNode>(null);
const [employeeModal, setEmployeeModal] = useState(false); const [employeeModal, setEmployeeModal] = useState(false);
const [choosedItem, setChooseItem] = useState<ChoosedItemType>({ const [choosedItem, setChooseItem] = useState<ChoosedItemType>({
nickName: "", nickName: '',
phone: "", phone: '',
role: [], role: [],
avatar: "", avatar: '',
}); });
const [roleIds, setRoleIds] = useState<Array<RoleItemType>>([]); const [roleIds, setRoleIds] = useState<Array<RoleItemType>>([]);
const storeId = User.getStoreId(); const storeId = User.getStoreId();
const StoreType = User.getStoreType(); const StoreType = User.getStoreType();
const isWorkWechat = !!(StoreType === "WE_CHAT_STORE"); const isWorkWechat = !!(StoreType === 'WE_CHAT_STORE');
useEffect(() => { useEffect(() => {
if (!User.getEnterpriseId()) { if (!User.getEnterpriseId()) {
...@@ -94,14 +94,14 @@ function EmployeeManage() { ...@@ -94,14 +94,14 @@ function EmployeeManage() {
const _query = { const _query = {
current: 0, current: 0,
size: 10, size: 10,
nickName: "", nickName: '',
phone: "", phone: '',
roleCodes: [], roleCodes: [],
} };
StoreService.getEmployeeList(_query).then((res: any) => { StoreService.getEmployeeList(_query).then((res: any) => {
setRealTotal(res.result.total); setRealTotal(res.result.total);
}); });
}, []) }, []);
useEffect(() => { useEffect(() => {
getEmployeeList(); getEmployeeList();
...@@ -130,7 +130,7 @@ function EmployeeManage() { ...@@ -130,7 +130,7 @@ function EmployeeManage() {
const _query = { ...query }; const _query = { ...query };
let _data = _.filter(data, (_item) => { let _data = _.filter(data, (_item) => {
return _item.roleCode !== "StoreManager"; return _item.roleCode !== 'StoreManager';
}); });
setRoleIds(_data); setRoleIds(_data);
...@@ -145,29 +145,21 @@ function EmployeeManage() { ...@@ -145,29 +145,21 @@ function EmployeeManage() {
fixed:'left', fixed:'left',
render: (val: string, record: RecordTypes) => { render: (val: string, record: RecordTypes) => {
return ( return (
<div className="employee-info"> <div className='employee-info'>
{isWorkWechat && ( {isWorkWechat && <img src={record.avatar || 'https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png'} alt='' />}
<img
src={
record.avatar ||
"https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png"
}
alt=""
/>
)}
{/* <span className="title">{val}</span> */} {/* <span className="title">{val}</span> */}
<span className="title"> <span className='title'>
<WWOpenDataCom type="userName" openid={val}/> <WWOpenDataCom type='userName' openid={val} />
</span> </span>
</div> </div>
); );
}, },
}, },
{ {
title: "手机号", title: '手机号',
dataIndex: "phone", dataIndex: 'phone',
key: "phone", key: 'phone',
render: (val: string) => { render: (val: string) => {
return <div>{val || '-'}</div>; return <div>{val || '-'}</div>;
}, },
...@@ -180,8 +172,8 @@ function EmployeeManage() { ...@@ -180,8 +172,8 @@ function EmployeeManage() {
if(!record.depNameList){ if(!record.depNameList){
return <span>-</span> return <span>-</span>
} }
if(record.depNameList.length === 0){ if (record.depNameList.length === 0) {
return <span>-</span> return <span>-</span>;
} }
return <Tooltip title={<div>{handleDepName(record.depNameList)}</div>} placement='top' arrowPointAtCenter><div className="post-name"> {record.depNameList.map((item:any, index:any) => { return <Tooltip title={<div>{handleDepName(record.depNameList)}</div>} placement='top' arrowPointAtCenter><div className="post-name"> {record.depNameList.map((item:any, index:any) => {
return <span><WWOpenDataCom type="departmentName" openid={item}/>{index<(record.depNameList.length -1)?';':''}</span> return <span><WWOpenDataCom type="departmentName" openid={item}/>{index<(record.depNameList.length -1)?';':''}</span>
...@@ -195,7 +187,7 @@ function EmployeeManage() { ...@@ -195,7 +187,7 @@ function EmployeeManage() {
dataIndex: "role", dataIndex: "role",
key: "role", key: "role",
render: (val: string) => { render: (val: string) => {
return <div>{val.split(",").join("、")}</div>; return <div>{val.split(',').join('、')}</div>;
}, },
}, },
{ {
...@@ -203,34 +195,19 @@ function EmployeeManage() { ...@@ -203,34 +195,19 @@ function EmployeeManage() {
dataIndex: "operation", dataIndex: "operation",
fixed:'right', fixed:'right',
render: (val: string, record: RecordTypes) => { render: (val: string, record: RecordTypes) => {
return record.role === "学院管理员" || return record.role === '学院管理员' || record.userId === User.getUserId() ? (
record.userId === User.getUserId() ? ( <div className='no-operate'>-</div>
<div className="no-operate">-</div>
) : ( ) : (
<div className="operation"> <div className='operation'>
{!record.depNameList && <span className='edit' onClick={() => handleEditEmployee(record)}>
<span className="edit edit-disable"> 编辑
编辑 </span>
</span> <span className='divider-line'>{' | '}</span>
}
{record.depNameList &&
(record.depNameList.length > 0 ?
<span className="edit" onClick={() => handleEditEmployee(record)}>
编辑
</span>
:
<span className="edit edit-disable">
编辑
</span>
)
}
<span className="divider-line">{" | "}</span>
<span <span
className="delete" className='delete'
onClick={() =>{ onClick={() => {
handleDeleteWorkWechatEmployeeConfirm(record) handleDeleteWorkWechatEmployeeConfirm(record);
}} }}>
>
删除 删除
</span> </span>
</div> </div>
...@@ -241,9 +218,9 @@ function EmployeeManage() { ...@@ -241,9 +218,9 @@ function EmployeeManage() {
if (isWorkWechat && columns) { if (isWorkWechat && columns) {
const item = { const item = {
title: "企业微信账号", title: '企业微信账号',
dataIndex: "weChatAccount", dataIndex: 'weChatAccount',
key: "weChatAccount", key: 'weChatAccount',
render: (val: string) => { render: (val: string) => {
return <div>{val}</div>; return <div>{val}</div>;
}, },
...@@ -253,15 +230,19 @@ function EmployeeManage() { ...@@ -253,15 +230,19 @@ function EmployeeManage() {
return columns; return columns;
} }
function handleDepName(depArray:any):any{ function handleDepName(depArray: any): any {
const depArrayDom = depArray.map((item:any, index:any) => { const depArrayDom = depArray.map((item: any, index: any) => {
return <span><WWOpenDataCom type="departmentName" openid={item}/></span> return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
</span>
);
}); });
return depArrayDom; return depArrayDom;
}; }
function handleEditEmployee(record: RecordTypes) { function handleEditEmployee(record: RecordTypes) {
const { nickName, phone, roleCodes, avatar, id, weChatAccount,depNameList} = record; const { nickName, phone, roleCodes, avatar, id, weChatAccount, depNameList } = record;
const _choosesItem = { const _choosesItem = {
nickName: nickName, nickName: nickName,
phone: phone, phone: phone,
...@@ -269,7 +250,7 @@ function EmployeeManage() { ...@@ -269,7 +250,7 @@ function EmployeeManage() {
avatar: avatar, avatar: avatar,
storeUserId: id, storeUserId: id,
weChatAccount, weChatAccount,
depNameList:depNameList depNameList: depNameList,
}; };
setChooseItem(_choosesItem); setChooseItem(_choosesItem);
const model: React.ReactNode = ( const model: React.ReactNode = (
...@@ -279,13 +260,13 @@ function EmployeeManage() { ...@@ -279,13 +260,13 @@ function EmployeeManage() {
setModel(null); setModel(null);
getEmployeeList(); getEmployeeList();
setChooseItem({ setChooseItem({
nickName: "", nickName: '',
phone: "", phone: '',
role: [], role: [],
avatar: "", avatar: '',
storeUserId: "", storeUserId: '',
weChatAccount:"", weChatAccount: '',
depNameList:[] depNameList: [],
}); });
}} }}
isWorkWechat={isWorkWechat} isWorkWechat={isWorkWechat}
...@@ -296,14 +277,12 @@ function EmployeeManage() { ...@@ -296,14 +277,12 @@ function EmployeeManage() {
function handleDeleteWorkWechatEmployeeConfirm(record: RecordTypes) { function handleDeleteWorkWechatEmployeeConfirm(record: RecordTypes) {
return confirm({ return confirm({
title: "你确定要删除此员工吗?", title: '你确定要删除此员工吗?',
content: "删除后数据无法恢复", content: '删除后数据无法恢复',
icon: ( icon: <span className='icon iconfont default-confirm-icon'>&#xe839; </span>,
<span className="icon iconfont default-confirm-icon">&#xe839; </span> okText: '删除',
), okType: 'danger',
okText: "删除", cancelText: '取消',
okType: "danger",
cancelText: "取消",
onOk: () => { onOk: () => {
handleDeleteEmployee(record.id); handleDeleteEmployee(record.id);
}, },
...@@ -316,21 +295,21 @@ function EmployeeManage() { ...@@ -316,21 +295,21 @@ function EmployeeManage() {
function handleDeleteEmployee(storeUserId: string) { function handleDeleteEmployee(storeUserId: string) {
StoreService.deleteEmployee({ storeUserId }).then((res: any) => { StoreService.deleteEmployee({ storeUserId }).then((res: any) => {
const msg = isWorkWechat ? "员工已删除":"讲师已删除"; const msg = isWorkWechat ? '员工已删除' : '讲师已删除';
message.success(msg); message.success(msg);
getEmployeeList(); getEmployeeList();
}); });
} }
function updateListData(){ function updateListData() {
let num:any; let num: any;
const params = { const params = {
enterpriseId:User.getEnterpriseId(), enterpriseId: User.getEnterpriseId(),
storeId:User.getStoreId(), storeId: User.getStoreId(),
userType:'USER' userType: 'USER',
} };
StoreService.getSyncCount(params).then((res) => { StoreService.getSyncCount(params).then((res) => {
num = res.result; num = res.result;
if(num<3){ if (num < 3) {
return confirm({ return confirm({
title: "确定更新列表数据吗?", title: "确定更新列表数据吗?",
content: <span>员工数据来源企微通讯录,一天只能更新3次,今日还能更新<span style={{color:'#2966FF'}}>{3-num}</span>次。</span>, content: <span>员工数据来源企微通讯录,一天只能更新3次,今日还能更新<span style={{color:'#2966FF'}}>{3-num}</span>次。</span>,
...@@ -340,10 +319,10 @@ function EmployeeManage() { ...@@ -340,10 +319,10 @@ function EmployeeManage() {
okText: "确定", okText: "确定",
cancelText: "取消", cancelText: "取消",
onOk: () => { onOk: () => {
confirmUpdateListData() confirmUpdateListData();
}, },
}); });
}else{ } else {
Modal.warning({ Modal.warning({
title: '提示', title: '提示',
okText: '我知道了', okText: '我知道了',
...@@ -356,109 +335,101 @@ function EmployeeManage() { ...@@ -356,109 +335,101 @@ function EmployeeManage() {
}); });
} }
}); });
} }
function confirmUpdateListData(){ function confirmUpdateListData() {
const params = { const params = {
enterpriseId:User.getEnterpriseId(), enterpriseId: User.getEnterpriseId(),
storeId:User.getStoreId(), storeId: User.getStoreId(),
userType:'USER' userType: 'USER',
} };
StoreService.syncWorkWeChatDepartment(params).then((res) => { StoreService.syncWorkWeChatDepartment(params).then((res) => {
getEmployeeList(); getEmployeeList();
message.success('已更新'); message.success('已更新');
}); });
} }
return ( return (
<div className="page employee-manage-page"> <div className='page employee-manage-page'>
<div className="content-header">角色管理</div> <div className='content-header'>角色管理</div>
<div className="box"> <div className='box'>
<div className="box-header"> <div className='box-header'>
<div <div
style={{ style={{
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "flex-start", justifyContent: 'flex-start',
padding: "0px 0 4px", padding: '0px 0 4px',
}} }}>
> <div>
<div>
搜索员工: 搜索员工:
<Search <Search
style={{ style={{
width: 300, width: 300,
marginRight: 40, marginRight: 40,
}} }}
placeholder={ placeholder={isWorkWechat ? '请输入员工昵称' : '搜索员工昵称/手机号'}
isWorkWechat ? "请输入员工昵称" : "搜索员工昵称/手机号" onSearch={(value) => {
const _query = { ...query };
// 企业微信用户只能搜索员工昵称
if (isWorkWechat) {
_query.nickName = value;
_query.current = 0;
setQuery(_query);
return;
} }
onSearch={(value) => {
const _query = { ...query };
// 企业微信用户只能搜索员工昵称
if (isWorkWechat) {
_query.nickName = value;
_query.current = 0;
setQuery(_query);
return;
}
if (value) { if (value) {
const isPhone = (value || "").match(/^\d+$/); const isPhone = (value || '').match(/^\d+$/);
const name = isPhone ? "phone" : "nickName"; const name = isPhone ? 'phone' : 'nickName';
const otherName = isPhone ? "nickName" : "phone"; const otherName = isPhone ? 'nickName' : 'phone';
_query[name] = value; _query[name] = value;
_query[otherName] = ""; _query[otherName] = '';
_query.current = 0; _query.current = 0;
} else { } else {
_query.nickName = ""; _query.nickName = '';
_query.phone = ""; _query.phone = '';
_query.current = 0; _query.current = 0;
} }
setQuery(_query); setQuery(_query);
}} }}
enterButton={<span className="icon iconfont">&#xe832;</span>} enterButton={<span className='icon iconfont'>&#xe832;</span>}
/> />
</div> </div>
<div> <div>
角色: 角色:
{_.map(roleIds, (item: any) => { {_.map(roleIds, (item: any) => {
return ( return (
<CheckBox <CheckBox
key={item.roleCode} key={item.roleCode}
text={item.name} text={item.name}
name={item.roleCode} name={item.roleCode}
onChange={(e: any) => { onChange={(e: any) => {
const { checked, name } = e.target; const { checked, name } = e.target;
const _query = { ...query }; const _query = { ...query };
_query.roleCodes = []; _query.roleCodes = [];
_query.current = 0; _query.current = 0;
const _roleIds: Array<RoleItemType> = roleIds.map( const _roleIds: Array<RoleItemType> = roleIds.map((_item: RoleItemType) => {
(_item: RoleItemType) => { if (name === _item.roleCode) {
if (name === _item.roleCode) { if (checked) {
if (checked) { _item.isChecked = true;
_item.isChecked = true; } else {
} else { _item.isChecked = false;
_item.isChecked = false;
}
}
if (_item.isChecked) {
_query.roleCodes.push(_item.roleCode);
}
return _item;
} }
); }
if (_item.isChecked) {
_query.roleCodes.push(_item.roleCode);
}
return _item;
});
setRoleIds(_roleIds); setRoleIds(_roleIds);
setQuery(_query); setQuery(_query);
}} }}
defaultChecked={item.isChecked} defaultChecked={item.isChecked}
/> />
); );
})} })}
</div>
</div> </div>
{(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && {(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') &&
<> <>
...@@ -510,21 +481,84 @@ function EmployeeManage() { ...@@ -510,21 +481,84 @@ function EmployeeManage() {
}} }}
/> />
</div> </div>
{(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<>
<Button
onClick={() => {
handleToAddEmployee();
}}
type='primary'
className='add-show-btn'>
添加员工
</Button>
<Button
className='update-user-btn'
onClick={() => {
updateListData();
}}>
更新列表数据
</Button>
<span className='origin-text'>数据来源企业微信通讯录</span>
<a href='https://www.yuque.com/wangzhong-zkqw0/qixue' target='_blank'>
<span className='view-text'>查看数据更新说明</span>
</a>
</>
)}
</div>
<LimitTip
type='员工'
total={realTotal}
tip={() => {
return <div>数据为当前学院的员工数,若员工存在多个学院,企业人数只统计为1人</div>;
}}
/>
<div className='box-body'>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
size={'middle'}
pagination={false}
dataSource={employeeList}
columns={parseColumn()}
rowKey={(item: any) => item.id}
bordered
/>
</div> </div>
{model} {model}
{ employeeModal && {employeeModal && (
<NewChooseMembersModal <NewChooseMembersModal
treeDepType="DEP_CHAT" treeDepType='DEP_CHAT'
visible={employeeModal} visible={employeeModal}
type="USER" type='USER'
close={()=>{setEmployeeModal(false)}} close={() => {
setEmployeeModal(false);
}}
onConfirm={() => { onConfirm={() => {
setEmployeeModal(false) setEmployeeModal(false);
message.success('添加成功') message.success('添加成功');
getEmployeeList(); getEmployeeList();
}} }}
/>
)}
{model}
{employeeModal && (
<NewChooseMembersModal
treeDepType='DEP_CHAT'
visible={employeeModal}
type='USER'
close={() => {
setEmployeeModal(false);
}}
onConfirm={() => {
setEmployeeModal(false);
message.success('添加成功');
getEmployeeList();
}}
/> />
} )}
</div>
</div> </div>
); );
} }
......
...@@ -5,7 +5,6 @@ import './SetEmployeeModal.less' ...@@ -5,7 +5,6 @@ import './SetEmployeeModal.less'
const RadioGroup = Radio.Group; const RadioGroup = Radio.Group;
const tagMap = { const tagMap = {
Cloud_Operator: '运营师',
Cloud_Lecturer: '讲师', Cloud_Lecturer: '讲师',
Cloud_Manager: '管理员', Cloud_Manager: '管理员',
} }
...@@ -105,12 +104,6 @@ export default class SetEmployeeModal extends React.Component { ...@@ -105,12 +104,6 @@ export default class SetEmployeeModal extends React.Component {
}} }}
className="mt5" className="mt5"
> >
<Radio value={"Cloud_Operator"} className="mt-4">
<span style={{ color: "#333" }}>运营师</span>
<p className="radio-tip">
仅可查看/转发培训计划内容,并查看负责的学员学习进度
</p>
</Radio>
<Radio value={"Cloud_Lecturer"} className="mt-4"> <Radio value={"Cloud_Lecturer"} className="mt-4">
<span style={{ color: "#333" }}>讲师</span> <span style={{ color: "#333" }}>讲师</span>
<p className="radio-tip"> <p className="radio-tip">
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: yuananting * @Author: yuananting
* @Date: 2021-07-05 10:47:19 * @Date: 2021-07-05 10:47:19
* @LastEditors: yuananting * @LastEditors: yuananting
* @LastEditTime: 2021-07-12 17:13:38 * @LastEditTime: 2021-08-02 17:54:13
* @Description: 描述一下咯 * @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
...@@ -49,7 +49,7 @@ class GraphicsEditor extends React.Component { ...@@ -49,7 +49,7 @@ class GraphicsEditor extends React.Component {
renderEditor() { renderEditor() {
const { editorId } = this.state; const { editorId } = this.state;
const { detail, onChange, isIntro, maxLimit, editorType } = this.props; const { detail, onChange, isIntro, maxLimit, editorType, placeholder = '请输入正文' } = this.props;
class ImageMenu extends BtnMenu { class ImageMenu extends BtnMenu {
constructor(editor) { constructor(editor) {
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述 // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
...@@ -89,6 +89,7 @@ class GraphicsEditor extends React.Component { ...@@ -89,6 +89,7 @@ class GraphicsEditor extends React.Component {
this.editorInt = new E(`#editor${editorId}`); this.editorInt = new E(`#editor${editorId}`);
this.editorInt.config.focus = false; this.editorInt.config.focus = false;
this.editorInt.config.showFullScreen = !isIntro; this.editorInt.config.showFullScreen = !isIntro;
this.editorInt.config.placeholder = placeholder;
this.editorInt.menus.extend('xmimage', ImageMenu); this.editorInt.menus.extend('xmimage', ImageMenu);
!isIntro && this.editorInt.menus.extend('xmvideo', VideoMenu); !isIntro && this.editorInt.menus.extend('xmvideo', VideoMenu);
this.editorInt.config.menus = isIntro this.editorInt.config.menus = isIntro
......
...@@ -28,7 +28,7 @@ import Bus from '@/core/bus'; ...@@ -28,7 +28,7 @@ import Bus from '@/core/bus';
import './LiveCourseList.less'; import './LiveCourseList.less';
import { brandLiveName } from '@/domains/brand/constants'; import { brandLiveName } from '@/domains/brand/constants';
const { confirm } = Modal const { confirm } = Modal;
const courseStateShow = { const courseStateShow = {
UN_START: { UN_START: {
code: 1, code: 1,
...@@ -50,20 +50,20 @@ const courseStateShow = { ...@@ -50,20 +50,20 @@ const courseStateShow = {
title: '未成功开课', title: '未成功开课',
color: '#999', color: '#999',
}, },
} };
class LiveCourseList extends React.Component { class LiveCourseList extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
columns: [], columns: [],
openDownloadModal: false, openDownloadModal: false,
url: '', url: '',
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
selectPlanList: {}, selectPlanList: {},
} };
} }
componentWillMount() { componentWillMount() {
this.parseColumns() this.parseColumns();
} }
componentDidMount() { componentDidMount() {
this.getDownloadVersion() this.getDownloadVersion()
...@@ -73,13 +73,12 @@ class LiveCourseList extends React.Component { ...@@ -73,13 +73,12 @@ class LiveCourseList extends React.Component {
} }
// 显示分享弹窗 // 显示分享弹窗
handleShowShareModal = (item, needStr = false) => { handleShowShareModal = (item, needStr = false) => {
const { liveCourseId } = item const { liveCourseId } = item;
const htmlUrl = `${LIVE_SHARE}live_detail/${liveCourseId}?id=${User.getStoreId()}` const htmlUrl = `${LIVE_SHARE}live_detail/${liveCourseId}?id=${User.getStoreId()}`;
const longUrl = htmlUrl const longUrl = htmlUrl;
console.log('htmlUrl', htmlUrl, longUrl)
const shareData = { ...item, longUrl } const shareData = { ...item, longUrl };
const shareLiveModal = ( const shareLiveModal = (
<ShareLiveModal <ShareLiveModal
needStr={needStr} needStr={needStr}
...@@ -89,33 +88,33 @@ class LiveCourseList extends React.Component { ...@@ -89,33 +88,33 @@ class LiveCourseList extends React.Component {
close={() => { close={() => {
this.setState({ this.setState({
shareLiveModal: null, shareLiveModal: null,
}) });
localStorage.setItem('largeLiveCourseItem', '') localStorage.setItem('largeLiveCourseItem', '');
}} }}
/> />
) );
this.setState({ shareLiveModal }) this.setState({ shareLiveModal });
} };
//改变上架状态 //改变上架状态
changeShelfState = (index, item, checked) => { changeShelfState = (index, item, checked) => {
let _shelfState = checked ? 'YES' : 'NO' let _shelfState = checked ? 'YES' : 'NO';
const params = { const params = {
liveCourseId: item.liveCourseId, liveCourseId: item.liveCourseId,
shelfState: _shelfState, shelfState: _shelfState,
} };
CourseService.turnOnOrOffLiveCloudCourse(params).then((res) => { CourseService.turnOnOrOffLiveCloudCourse(params).then((res) => {
if (res.success) { if (res.success) {
if (_shelfState === 'YES') { if (_shelfState === 'YES') {
message.success('已开启展示') message.success('已开启展示');
} else { } else {
message.success('已取消展示') message.success('已取消展示');
} }
this.props.changeShelfState(index, _shelfState) this.props.changeShelfState(index, _shelfState);
} }
}) });
} };
// 前往上课数据页面 // 前往上课数据页面
handleLinkToClassData = (item) => { handleLinkToClassData = (item) => {
...@@ -126,8 +125,8 @@ class LiveCourseList extends React.Component { ...@@ -126,8 +125,8 @@ class LiveCourseList extends React.Component {
}); });
}; };
parseColumns = () => { parseColumns = () => {
let columns let columns;
const userRole = User.getUserRole() const userRole = User.getUserRole();
if (userRole !== 'CloudLecturer') { if (userRole !== 'CloudLecturer') {
columns = [ columns = [
{ {
...@@ -137,15 +136,15 @@ class LiveCourseList extends React.Component { ...@@ -137,15 +136,15 @@ class LiveCourseList extends React.Component {
fixed: 'left', fixed: 'left',
dataIndex: 'courseName', dataIndex: 'courseName',
render: (val, record) => { render: (val, record) => {
let hasCover = false let hasCover = false;
return ( return (
<div className='record__item'> <div className='record__item'>
{record.courseMediaVOS.map((item, index) => { {record.courseMediaVOS.map((item, index) => {
if (item.contentType === 'COVER') { if (item.contentType === 'COVER') {
hasCover = true hasCover = true;
return <img key={item.mediaContent + index} className='course-cover' src={item.mediaUrl} alt='' /> return <img key={item.mediaContent + index} className='course-cover' src={item.mediaUrl} alt='' />;
} else { } else {
return null return null;
} }
})} })}
{!hasCover && <img className='course-cover' src={'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'} alt='' />} {!hasCover && <img className='course-cover' src={'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'} alt='' />}
...@@ -191,7 +190,7 @@ class LiveCourseList extends React.Component { ...@@ -191,7 +190,7 @@ class LiveCourseList extends React.Component {
<span key={item.adminId + index}> <span key={item.adminId + index}>
{item.adminName} {index < record.admins.length - 1 && <span></span>}{' '} {item.adminName} {index < record.admins.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</span> </span>
</Tooltip> </Tooltip>
...@@ -204,7 +203,7 @@ class LiveCourseList extends React.Component { ...@@ -204,7 +203,7 @@ class LiveCourseList extends React.Component {
<span> <span>
{item.adminName} {index < record.admins.length - 1 && <span></span>}{' '} {item.adminName} {index < record.admins.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</span> </span>
</Otherwise> </Otherwise>
...@@ -213,7 +212,7 @@ class LiveCourseList extends React.Component { ...@@ -213,7 +212,7 @@ class LiveCourseList extends React.Component {
</div> </div>
</div> </div>
</div> </div>
) );
}, },
}, },
{ {
...@@ -244,7 +243,7 @@ class LiveCourseList extends React.Component { ...@@ -244,7 +243,7 @@ class LiveCourseList extends React.Component {
key: 'couseCatalog', key: 'couseCatalog',
dataIndex: 'couseCatalog', dataIndex: 'couseCatalog',
render: (val, item) => { render: (val, item) => {
return <div className='categoryName'>{item.categorySonName}</div> return <div className='categoryName'>{item.categorySonName}</div>;
}, },
}, },
{ {
...@@ -260,11 +259,11 @@ class LiveCourseList extends React.Component { ...@@ -260,11 +259,11 @@ class LiveCourseList extends React.Component {
this.setState({ this.setState({
editData: item, editData: item,
openCoursewareModal: true, openCoursewareModal: true,
}) });
}}> }}>
{item.courseDocumentCount} {item.courseDocumentCount}
</span> </span>
) );
}, },
}, },
{ {
...@@ -277,11 +276,11 @@ class LiveCourseList extends React.Component { ...@@ -277,11 +276,11 @@ class LiveCourseList extends React.Component {
<span <span
className='iconfont icon quota-icon' className='iconfont icon quota-icon'
onClick={() => { onClick={() => {
this.handleLinkToClassData(item) this.handleLinkToClassData(item);
}}> }}>
&#xe7d6; &#xe7d6;
</span> </span>
) );
}, },
}, },
{ {
...@@ -313,7 +312,7 @@ class LiveCourseList extends React.Component { ...@@ -313,7 +312,7 @@ class LiveCourseList extends React.Component {
defaultChecked={item.shelfState === 'YES' ? true : false} defaultChecked={item.shelfState === 'YES' ? true : false}
onChange={(checked) => this.changeShelfState(index, item, checked)} onChange={(checked) => this.changeShelfState(index, item, checked)}
/> />
) );
}, },
}, },
{ {
...@@ -324,7 +323,7 @@ class LiveCourseList extends React.Component { ...@@ -324,7 +323,7 @@ class LiveCourseList extends React.Component {
sorter: true, sorter: true,
render: (val, item) => { render: (val, item) => {
// -29000:与后端约定 在初始化学院时,创建时间(标志位-29000)默认展示为'-' // -29000:与后端约定 在初始化学院时,创建时间(标志位-29000)默认展示为'-'
return <span style={{ whiteSpace: 'nowrap' }}>{val === -29000 ? '-' : window.formatDate('YYYY-MM-DD H:i', val)}</span> return <span style={{ whiteSpace: 'nowrap' }}>{val === -29000 ? '-' : window.formatDate('YYYY-MM-DD H:i', val)}</span>;
}, },
}, },
{ {
...@@ -343,7 +342,7 @@ class LiveCourseList extends React.Component { ...@@ -343,7 +342,7 @@ class LiveCourseList extends React.Component {
<span key={index}> <span key={index}>
{item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '} {item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</Tooltip> </Tooltip>
</When> </When>
...@@ -352,7 +351,7 @@ class LiveCourseList extends React.Component { ...@@ -352,7 +351,7 @@ class LiveCourseList extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
...@@ -426,7 +425,7 @@ class LiveCourseList extends React.Component { ...@@ -426,7 +425,7 @@ class LiveCourseList extends React.Component {
key='share' key='share'
className='operate__item' className='operate__item'
onClick={() => { onClick={() => {
this.handleShowShareModal(item) this.handleShowShareModal(item);
}}> }}>
分享 分享
</div> </div>
...@@ -456,10 +455,10 @@ class LiveCourseList extends React.Component { ...@@ -456,10 +455,10 @@ class LiveCourseList extends React.Component {
</div> </div>
)} )}
</div> </div>
) );
}, },
}, },
] ];
} else { } else {
columns = [ columns = [
{ {
...@@ -468,13 +467,13 @@ class LiveCourseList extends React.Component { ...@@ -468,13 +467,13 @@ class LiveCourseList extends React.Component {
key: 'course', key: 'course',
dataIndex: 'courseName', dataIndex: 'courseName',
render: (val, record) => { render: (val, record) => {
let hasCover = false let hasCover = false;
return ( return (
<div className='record__item'> <div className='record__item'>
{record.courseMediaVOS.map((item, index) => { {record.courseMediaVOS.map((item, index) => {
if (item.contentType === 'COVER') { if (item.contentType === 'COVER') {
hasCover = true hasCover = true;
return <img className='course-cover' src={item.mediaUrl} alt='' /> return <img className='course-cover' src={item.mediaUrl} alt='' />;
} }
})} })}
{!hasCover && <img className='course-cover' src={'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'} alt='' />} {!hasCover && <img className='course-cover' src={'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'} alt='' />}
...@@ -519,7 +518,7 @@ class LiveCourseList extends React.Component { ...@@ -519,7 +518,7 @@ class LiveCourseList extends React.Component {
<span> <span>
{item.adminName} {index < record.admins.length - 1 && <span></span>}{' '} {item.adminName} {index < record.admins.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</span> </span>
</Tooltip> </Tooltip>
...@@ -532,7 +531,7 @@ class LiveCourseList extends React.Component { ...@@ -532,7 +531,7 @@ class LiveCourseList extends React.Component {
<span> <span>
{item.adminName} {index < record.admins.length - 1 && <span></span>}{' '} {item.adminName} {index < record.admins.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</span> </span>
</Otherwise> </Otherwise>
...@@ -541,7 +540,7 @@ class LiveCourseList extends React.Component { ...@@ -541,7 +540,7 @@ class LiveCourseList extends React.Component {
</div> </div>
</div> </div>
</div> </div>
) );
}, },
}, },
{ {
...@@ -550,7 +549,7 @@ class LiveCourseList extends React.Component { ...@@ -550,7 +549,7 @@ class LiveCourseList extends React.Component {
key: 'couseCatalog', key: 'couseCatalog',
dataIndex: 'couseCatalog', dataIndex: 'couseCatalog',
render: (val, item) => { render: (val, item) => {
return <div className='categoryName'>{item.categorySonName}</div> return <div className='categoryName'>{item.categorySonName}</div>;
}, },
}, },
{ {
...@@ -566,11 +565,11 @@ class LiveCourseList extends React.Component { ...@@ -566,11 +565,11 @@ class LiveCourseList extends React.Component {
this.setState({ this.setState({
editData: item, editData: item,
openCoursewareModal: true, openCoursewareModal: true,
}) });
}}> }}>
{item.courseDocumentCount} {item.courseDocumentCount}
</span> </span>
) );
}, },
}, },
{ {
...@@ -583,11 +582,11 @@ class LiveCourseList extends React.Component { ...@@ -583,11 +582,11 @@ class LiveCourseList extends React.Component {
<span <span
className='iconfont icon quota-icon' className='iconfont icon quota-icon'
onClick={() => { onClick={() => {
this.handleLinkToClassData(item) this.handleLinkToClassData(item);
}}> }}>
&#xe7d6; &#xe7d6;
</span> </span>
) );
}, },
}, },
{ {
...@@ -597,7 +596,7 @@ class LiveCourseList extends React.Component { ...@@ -597,7 +596,7 @@ class LiveCourseList extends React.Component {
dataIndex: 'created', dataIndex: 'created',
sorter: true, sorter: true,
render: (val, item) => { render: (val, item) => {
return <span>{window.formatDate('YYYY-MM-DD H:i', val)}</span> return <span>{window.formatDate('YYYY-MM-DD H:i', val)}</span>;
}, },
}, },
{ {
...@@ -616,7 +615,7 @@ class LiveCourseList extends React.Component { ...@@ -616,7 +615,7 @@ class LiveCourseList extends React.Component {
<span> <span>
{item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '} {item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</Tooltip> </Tooltip>
</When> </When>
...@@ -625,36 +624,36 @@ class LiveCourseList extends React.Component { ...@@ -625,36 +624,36 @@ class LiveCourseList extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
] ];
} }
this.setState({ columns }) this.setState({ columns });
} };
handleAdminName = (adminArray) => { handleAdminName = (adminArray) => {
let adminStr = '' let adminStr = '';
adminArray.forEach((item, index) => { adminArray.forEach((item, index) => {
if (index < adminArray.length - 1) { if (index < adminArray.length - 1) {
adminStr = adminStr + item.adminName + '、' adminStr = adminStr + item.adminName + '、';
} else { } else {
adminStr = adminStr + item.adminName adminStr = adminStr + item.adminName;
} }
}) });
return adminStr return adminStr;
} };
handlePlanName = (planArray) => { handlePlanName = (planArray) => {
let planStr = '' let planStr = '';
planArray.forEach((item, index) => { planArray.forEach((item, index) => {
if (index < planArray.length - 1) { if (index < planArray.length - 1) {
planStr = planStr + item.planName + '、' planStr = planStr + item.planName + '、';
} else { } else {
planStr = planStr + item.planName planStr = planStr + item.planName;
} }
}) });
return planStr return planStr;
} };
renderMoreOperate = (item) => { renderMoreOperate = (item) => {
let now = new Date().getTime() let now = new Date().getTime()
...@@ -721,7 +720,7 @@ class LiveCourseList extends React.Component { ...@@ -721,7 +720,7 @@ class LiveCourseList extends React.Component {
<div className='live-course-more-menu'> <div className='live-course-more-menu'>
{(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && ( {(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<div className='operate__item' onClick={() => this.handleRelatedModalShow(item)}> <div className='operate__item' onClick={() => this.handleRelatedModalShow(item)}>
关联培训计划 关联培训任务
</div> </div>
)} )}
<div className='operate__item' onClick={() => this.toEditCoursePage(item)}> <div className='operate__item' onClick={() => this.toEditCoursePage(item)}>
...@@ -746,10 +745,10 @@ class LiveCourseList extends React.Component { ...@@ -746,10 +745,10 @@ class LiveCourseList extends React.Component {
width: 440, width: 440,
height: 188, height: 188,
onOk: () => { onOk: () => {
this.deleteConfirm(record) this.deleteConfirm(record);
}, },
}) });
} };
deleteConfirm = (item) => { deleteConfirm = (item) => {
const params = { const params = {
liveCourseId: item.liveCourseId, liveCourseId: item.liveCourseId,
...@@ -785,8 +784,8 @@ class LiveCourseList extends React.Component { ...@@ -785,8 +784,8 @@ class LiveCourseList extends React.Component {
}; };
refreshCourseList = () => { refreshCourseList = () => {
this.props.onChange(this.props.query) this.props.onChange(this.props.query);
} };
//进入直播间 //进入直播间
handleEnterLiveRoom = (item) => { handleEnterLiveRoom = (item) => {
if (item.thirdPartType === "WECHAT") { if (item.thirdPartType === "WECHAT") {
...@@ -844,48 +843,48 @@ class LiveCourseList extends React.Component { ...@@ -844,48 +843,48 @@ class LiveCourseList extends React.Component {
&#xe834; &#xe834;
</span> </span>
), ),
}) });
} else { } else {
CourseService.getLiveCloudCourseDetail({ CourseService.getLiveCloudCourseDetail({
liveCourseId: item.liveCourseId, liveCourseId: item.liveCourseId,
}).then((res) => { }).then((res) => {
const url = `xmqx://liveCourseId=${item.liveCourseId}` const url = `xmqx://liveCourseId=${item.liveCourseId}`;
if (res.result.courseState === 'FINISH') { if (res.result.courseState === 'FINISH') {
Modal.warning({ Modal.warning({
title: '刷新页面', title: '刷新页面',
icon: <QuestionCircleOutlined />, icon: <QuestionCircleOutlined />,
content: '课次已结束,请刷新一下', content: '课次已结束,请刷新一下',
onOk: () => { onOk: () => {
this.refreshCourseList() this.refreshCourseList();
}, },
}) });
} else { } else {
this.setState({ url, openDownloadModal: true }) this.setState({ url, openDownloadModal: true });
} }
}) });
} }
} };
onShowSizeChange = (current, size) => { onShowSizeChange = (current, size) => {
if (current === size) { if (current === size) {
return return;
} }
let _query = this.props.query let _query = this.props.query;
_query.size = size _query.size = size;
this.props.onChange(_query) this.props.onChange(_query);
} };
getDownloadVersion() { getDownloadVersion() {
const isMac = /macintosh|mac os x/i.test(navigator.userAgent) const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
// 判断学员系统 // 判断学员系统
let platform let platform;
if (!isMac) { if (!isMac) {
platform = 1 platform = 1;
} else { } else {
platform = 4 platform = 4;
} }
BaseService.getLastedVersion({ model: 5, platform }).then((res) => { BaseService.getLastedVersion({ model: 5, platform }).then((res) => {
const { result = {} } = res const { result = {} } = res;
this.setState({ downloadUrl: result.releaseUrl }) this.setState({ downloadUrl: result.releaseUrl });
}) });
} }
handleViewPlayBack = (item) => { handleViewPlayBack = (item) => {
if (item.thirdPartType === "WECHAT") { if (item.thirdPartType === "WECHAT") {
...@@ -917,78 +916,79 @@ class LiveCourseList extends React.Component { ...@@ -917,78 +916,79 @@ class LiveCourseList extends React.Component {
} }
let htmlUrl; let htmlUrl;
if (item.teacherId === User.getUserId()) { if (item.teacherId === User.getUserId()) {
htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?teacherId=${User.getUserId()}&id=${User.getStoreId()}` htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?teacherId=${User.getUserId()}&id=${User.getStoreId()}`;
} else if (_.pluck(item.admins, 'adminId').includes(User.getUserId())) { } else if (_.pluck(item.admins, 'adminId').includes(User.getUserId())) {
htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?userId=${User.getUserId()}&id=${User.getStoreId()}` htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?userId=${User.getUserId()}&id=${User.getStoreId()}`;
} else { } else {
htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?id=${User.getStoreId()}` htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?id=${User.getStoreId()}`;
} }
window.open(htmlUrl) window.open(htmlUrl);
} };
handleRelatedModalShow = (item) => { handleRelatedModalShow = (item) => {
const selectPlanList = {} const selectPlanList = {};
if (item.relatedPlanList) { if (item.relatedPlanList) {
item.relatedPlanList.map((item, index) => { item.relatedPlanList.map((childItem, index) => {
selectPlanList[item.planId] = {} selectPlanList[childItem.taskId] = {};
selectPlanList[item.planId].planId = item.planId selectPlanList[childItem.taskId].taskId = childItem.taskId;
selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }] selectPlanList[childItem.taskId].taskBaseVOList = [{ stageId: childItem.stageId }];
return item return item;
}) });
} }
this.setState({ this.setState({
RelatedPlanModalVisible: true, RelatedPlanModalVisible: true,
selectCourseId: item.liveCourseId, selectCourseId: item.liveCourseId,
selectPlanList: selectPlanList, selectPlanList: selectPlanList,
}) });
} };
closeRelatedPlanModalVisible = () => { closeRelatedPlanModalVisible = () => {
this.setState({ this.setState({
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}) });
} };
onChangeSelectPlanList = (selectPlanList) => { onChangeSelectPlanList = (selectPlanList) => {
console.log('selectPlanList', selectPlanList);
this.setState({ this.setState({
selectPlanList: selectPlanList, selectPlanList: selectPlanList,
}) });
} };
onConfirmSelectPlanList = () => { onConfirmSelectPlanList = () => {
this.setState( this.setState(
{ {
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}, },
() => { () => {
this.props.onChange() this.props.onChange();
} }
) );
} };
handleChangeTable = (pagination, filters, sorter) => { handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter const { columnKey, order } = sorter;
const { query } = this.props const { query } = this.props;
let _columnKey let _columnKey;
let _order let _order;
// 按创建时间升序排序 // 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') { if (columnKey === 'created' && order === 'ascend') {
_columnKey = 'CREATED' _columnKey = 'CREATED';
_order = 'SORT_ASC' _order = 'SORT_ASC';
} }
// 按创建时间降序排序 // 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { if (columnKey === 'created' && order === 'descend') {
_columnKey = 'CREATED' _columnKey = 'CREATED';
_order = 'SORT_DESC' _order = 'SORT_DESC';
} }
const _query = { const _query = {
...query, ...query,
sortMap: {}, sortMap: {},
} };
_query.sortMap[_columnKey] = _order _query.sortMap[_columnKey] = _order;
this.props.onChange(_query) this.props.onChange(_query);
} };
render() { render() {
const { total, query, courseList, loading } = this.props const { total, query, courseList, loading } = this.props;
const { current, size } = query const { current, size } = query;
const { openDownloadModal, downloadUrl, url, columns, openCoursewareModal, editData, RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state const { openDownloadModal, downloadUrl, url, columns, openCoursewareModal, editData, RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state;
const { match } = this.props const { match } = this.props;
return ( return (
<div className='live-course-list'> <div className='live-course-list'>
...@@ -1014,8 +1014,8 @@ class LiveCourseList extends React.Component { ...@@ -1014,8 +1014,8 @@ class LiveCourseList extends React.Component {
pageSize={size} pageSize={size}
total={parseInt(total)} total={parseInt(total)}
toPage={(page) => { toPage={(page) => {
const _query = { ...query, current: page + 1 } const _query = { ...query, current: page + 1 };
this.props.onChange(_query) this.props.onChange(_query);
}} }}
onShowSizeChange={this.onShowSizeChange} onShowSizeChange={this.onShowSizeChange}
/> />
...@@ -1026,8 +1026,8 @@ class LiveCourseList extends React.Component { ...@@ -1026,8 +1026,8 @@ class LiveCourseList extends React.Component {
<ManageCoursewareModal <ManageCoursewareModal
data={editData} data={editData}
onCancel={() => { onCancel={() => {
this.props.onChange() this.props.onChange();
this.setState({ openCoursewareModal: false }) this.setState({ openCoursewareModal: false });
}} }}
/> />
)} )}
...@@ -1038,7 +1038,7 @@ class LiveCourseList extends React.Component { ...@@ -1038,7 +1038,7 @@ class LiveCourseList extends React.Component {
this.setState({ this.setState({
url: '', url: '',
openDownloadModal: false, openDownloadModal: false,
}) });
}} }}
/> />
)} )}
...@@ -1055,8 +1055,8 @@ class LiveCourseList extends React.Component { ...@@ -1055,8 +1055,8 @@ class LiveCourseList extends React.Component {
<iframe src={url} style={{ display: 'none' }} title='navigation' /> <iframe src={url} style={{ display: 'none' }} title='navigation' />
<Route path={`${match.url}/live-course-data`} component={DataList} /> <Route path={`${match.url}/live-course-data`} component={DataList} />
</div> </div>
) );
} }
} }
export default withRouter(LiveCourseList) export default withRouter(LiveCourseList);
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-05 10:12:45 * @Date: 2020-08-05 10:12:45
* @LastEditors: yuananting * @LastEditors: wufan
* @LastEditTime: 2021-07-15 14:12:29 * @LastEditTime: 2021-09-03 10:36:29
* @Description: 线上课-列表模块 * @Description: 线上课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import User from '@/common/js/user' import User from '@/common/js/user';
import college from '@/common/lottie/college' import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components' import { PageControl, XMTable } from '@/components';
import { LIVE_SHARE } from '@/domains/course-domain/constants' import { LIVE_SHARE } from '@/domains/course-domain/constants';
import CourseService from '@/domains/course-domain/CourseService' import CourseService from '@/domains/course-domain/CourseService';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal' import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import { Dropdown, message, Modal, Switch, Tooltip } from 'antd' import { Dropdown, message, Modal, Switch, Tooltip } from 'antd';
import React from 'react' import React from 'react';
import RelatedPlanModal from '../../modal/RelatedPlanModal' import RelatedPlanModal from '../../modal/RelatedPlanModal';
import WatchDataModal from '../modal/WatchDataModal' import WatchDataModal from '../modal/WatchDataModal';
import './GraphicsCourseList.less' import './GraphicsCourseList.less';
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png' const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png';
class GraphicsCourseList extends React.Component { class GraphicsCourseList extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
id: '', // 视频课ID id: '', // 视频课ID
studentIds: [], studentIds: [],
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
selectPlanList: {}, selectPlanList: {},
} };
} }
componentDidMount() { componentDidMount() {
const videoCourseItem = localStorage.getItem('videoCourseItem') const videoCourseItem = localStorage.getItem('videoCourseItem');
if (videoCourseItem) { if (videoCourseItem) {
const _videoCourseItem = JSON.parse(videoCourseItem) const _videoCourseItem = JSON.parse(videoCourseItem);
this.handleShowShareModal(_videoCourseItem, true) this.handleShowShareModal(_videoCourseItem, true);
} }
} }
// 观看数据弹窗 // 观看数据弹窗
handleShowWatchDataModal = (record) => { handleShowWatchDataModal = (record) => {
const watchDataModal = ( const watchDataModal = (
<WatchDataModal <WatchDataModal
type='videoCourseList' type='videoCourseList'
data={record} data={record}
close={() => { close={() => {
this.setState({ this.setState({
watchDataModal: null, watchDataModal: null,
}) });
}} }}
/> />
) );
this.setState({ watchDataModal }) this.setState({ watchDataModal });
} };
handlePlanName = (planArray) => { handlePlanName = (planArray) => {
let planStr = '' let planStr = '';
planArray.forEach((item, index) => { planArray.forEach((item, index) => {
if (index < planArray.length - 1) { if (index < planArray.length - 1) {
planStr = planStr + item.planName + '、' planStr = planStr + item.planName + '、';
} else { } else {
planStr = planStr + item.planName planStr = planStr + item.planName;
} }
}) });
return planStr return planStr;
} };
// 请求表头 // 请求表头
parseColumns = () => { parseColumns = () => {
const columns = [ const columns = [
{ {
title: '图文课', title: '图文课',
key: 'scheduleName', key: 'scheduleName',
dataIndex: 'scheduleName', dataIndex: 'scheduleName',
width: 321, width: 321,
fixed: 'left', fixed: 'left',
render: (val, record) => { render: (val, record) => {
const { coverUrl } = record const { coverUrl } = record;
return ( return (
<div className='record__item'> <div className='record__item'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */} {/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img className='course-cover' src={coverUrl || defaultCoverUrl} alt='' /> <img className='course-cover' src={coverUrl || defaultCoverUrl} alt='' />
<Choose> <Choose>
<When condition={record.courseName.length > 25}> <When condition={record.courseName.length > 25}>
<Tooltip title={record.courseName}> <Tooltip title={record.courseName}>
<div className='course-name'>{record.courseName}</div> <div className='course-name'>{record.courseName}</div>
</Tooltip> </Tooltip>
</When> </When>
<Otherwise> <Otherwise>
<div className='course-name'>{record.courseName}</div> <div className='course-name'>{record.courseName}</div>
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
title: '课程分类', title: '课程分类',
key: 'categoryName', key: 'categoryName',
dataIndex: 'categoryName', dataIndex: 'categoryName',
width: 120, width: 120,
render: (val, record) => { render: (val, record) => {
return <div className='record__item'>{record.categorySonName}</div> return <div className='record__item'>{record.categorySonName}</div>;
}, },
}, },
{ {
title: '创建人', title: '创建人',
key: 'createName', key: 'createName',
dataIndex: 'createName', dataIndex: 'createName',
width: 100, width: 100,
render: (val) => { render: (val) => {
return ( return (
<div> <div>
{val && ( {val && (
<Tooltip title={val}> <Tooltip title={val}>
<div>{val.length > 4 ? `${val.slice(0, 4)}...` : val}</div> <div>{val.length > 4 ? `${val.slice(0, 4)}...` : val}</div>
</Tooltip> </Tooltip>
)} )}
</div> </div>
) );
}, },
}, },
{ {
title: ( title: (
<span> <span>
<span>学院展示</span> <span>学院展示</span>
<Tooltip <Tooltip
title={ title={
<div> <div>
开启后,学员可在学院内查看到此课程。 开启后,学员可在学院内查看到此课程。
<br /> <br />
关闭后,学院内不再展示此课程,但学员仍可通过分享的海报/链接查看此课程。 关闭后,学院内不再展示此课程,但学员仍可通过分享的海报/链接查看此课程。
</div> </div>
}> }>
<i className='icon iconfont' style={{ marginLeft: '5px', cursor: 'pointer', color: '#bfbfbf', fontSize: '14px', fontWeight: 'normal' }}> <i className='icon iconfont' style={{ marginLeft: '5px', cursor: 'pointer', color: '#bfbfbf', fontSize: '14px', fontWeight: 'normal' }}>
&#xe61d; &#xe61d;
</i> </i>
</Tooltip> </Tooltip>
</span> </span>
), ),
width: 120, width: 120,
dataIndex: 'courseware', dataIndex: 'courseware',
render: (val, item, index) => { render: (val, item, index) => {
return ( return (
<Switch <Switch
size='small' size='small'
checked={item.shelfState === 'YES'} checked={item.shelfState === 'YES'}
defaultChecked={item.shelfState === 'YES' ? true : false} defaultChecked={item.shelfState === 'YES' ? true : false}
onChange={(checked) => this.changeShelfState(index, item, checked)} onChange={(checked) => this.changeShelfState(index, item, checked)}
/> />
) );
}, },
}, },
{ {
title: '观看学员数', title: '观看学员数',
width: 110, width: 110,
key: 'watchUserCount', key: 'watchUserCount',
dataIndex: 'watchUserCount', dataIndex: 'watchUserCount',
render: (val, item) => { render: (val, item) => {
return <div className='watchUserCount'>{val}</div> return <div className='watchUserCount'>{val}</div>;
}, },
}, },
{ {
title: '创建时间', title: '创建时间',
width: 181, width: 181,
key: 'created', key: 'created',
dataIndex: 'created', dataIndex: 'created',
sorter: true, sorter: true,
render: (val) => { render: (val) => {
return window.formatDate('YYYY-MM-DD H:i', val) return window.formatDate('YYYY-MM-DD H:i', val);
}, },
}, },
{ {
title: '最近修改时间', title: '最近修改时间',
width: 181, width: 181,
key: 'updated', key: 'updated',
dataIndex: 'updated', dataIndex: 'updated',
sorter: true, sorter: true,
render: (val) => { render: (val) => {
return window.formatDate('YYYY-MM-DD H:i', val) return window.formatDate('YYYY-MM-DD H:i', val);
}, },
}, },
{ {
title: '关联项', title: '关联项',
width: 200, width: 200,
key: 'planList', key: 'planList',
dataIndex: 'planList', dataIndex: 'planList',
render: (val, record) => { render: (val, record) => {
return ( return (
<div className='related-task'> <div className='related-task'>
<Choose> <Choose>
<When condition={record.relatedPlanList}> <When condition={record.relatedPlanList}>
<Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter> <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter>
{record.relatedPlanList.map((item, index) => { {record.relatedPlanList.map((item, index) => {
return ( return (
<span key={item.planId}> <span key={item.taskId}>
{item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '} {item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</Tooltip> </Tooltip>
</When> </When>
<Otherwise> <Otherwise>
<span></span> <span></span>
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
title: '操作', title: '操作',
key: 'operate', key: 'operate',
dataIndex: 'operate', dataIndex: 'operate',
width: 210, width: 210,
fixed: 'right', fixed: 'right',
render: (val, record) => { render: (val, record) => {
return ( return (
<div className='operate'> <div className='operate'>
<div className='operate__item' onClick={() => this.handleShowWatchDataModal(record)}> <div className='operate__item' onClick={() => this.handleShowWatchDataModal(record)}>
观看数据 观看数据
</div> </div>
<span className='operate__item split'> | </span> <span className='operate__item split'> | </span>
<div className='operate__item' onClick={() => this.handleShowShareModal(record)}> <div className='operate__item' onClick={() => this.handleShowShareModal(record)}>
分享 分享
</div> </div>
<span className='operate__item split'> | </span> <span className='operate__item split'> | </span>
<Dropdown overlay={this.renderMoreOperate(record)}> <Dropdown overlay={this.renderMoreOperate(record)}>
<span className='more-operate'> <span className='more-operate'>
<span className='operate-text'>更多</span> <span className='operate-text'>更多</span>
<span className='iconfont icon' style={{ color: '#2966FF' }}> <span className='iconfont icon' style={{ color: '#2966FF' }}>
&#xe824; &#xe824;
</span> </span>
</span> </span>
</Dropdown> </Dropdown>
</div> </div>
) );
}, },
}, },
] ];
return columns return columns;
} };
handleRelatedModalShow = (item) => { handleRelatedModalShow = (item) => {
const selectPlanList = {} const selectPlanList = {};
if (item.relatedPlanList) { if (item.relatedPlanList) {
item.relatedPlanList.map((item, index) => { item.relatedPlanList.map((childItem, index) => {
selectPlanList[item.planId] = {} selectPlanList[childItem.taskId] = {};
selectPlanList[item.planId].planId = item.planId selectPlanList[childItem.taskId].taskId = childItem.taskId;
selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }] selectPlanList[childItem.taskId].taskBaseVOList = [{ stageId: childItem.stageId }];
return item return item;
}) });
} }
this.setState({ this.setState({
RelatedPlanModalVisible: true, RelatedPlanModalVisible: true,
selectCourseId: item.id, selectCourseId: item.id,
selectPlanList: selectPlanList, selectPlanList: selectPlanList,
}) });
} };
closeRelatedPlanModalVisible = () => { closeRelatedPlanModalVisible = () => {
this.setState({ this.setState({
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}) });
} };
onChangeSelectPlanList = (selectPlanList) => { onChangeSelectPlanList = (selectPlanList) => {
this.setState({ this.setState({
selectPlanList: selectPlanList, selectPlanList: selectPlanList,
}) });
} };
onConfirmSelectPlanList = () => { onConfirmSelectPlanList = () => {
this.setState( this.setState(
{ {
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}, },
() => { () => {
this.props.onChange() this.props.onChange();
} }
) );
} };
renderMoreOperate = (item) => { renderMoreOperate = (item) => {
return ( return (
<div className='live-course-more-menu'> <div className='live-course-more-menu'>
{(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && ( {(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<div <div
className='operate__item' className='operate__item'
key='plan' key='plan'
onClick={() => { onClick={() => {
this.handleRelatedModalShow(item) this.handleRelatedModalShow(item);
}}> }}>
关联培训计划 关联培训任务
</div> </div>
)} )}
<div <div
className='operate__item' className='operate__item'
key='edit' key='edit'
onClick={() => { onClick={() => {
window.RCHistory.push(`/create-graphics-course?type=edit&id=${item.id}`) window.RCHistory.push(`/create-graphics-course?type=edit&id=${item.id}`);
}}> }}>
编辑 编辑
</div> </div>
<div className='operate__item' key='delete' onClick={() => this.handleDeleteGraphicsCourse(item.id)}> <div className='operate__item' key='delete' onClick={() => this.handleDeleteGraphicsCourse(item.id)}>
删除 删除
</div> </div>
</div> </div>
) );
} };
//改变上架状态 //改变上架状态
changeShelfState = (index, item, checked) => { changeShelfState = (index, item, checked) => {
let _shelfState = checked ? 'YES' : 'NO' let _shelfState = checked ? 'YES' : 'NO';
const params = { const params = {
courseId: item.id, courseId: item.id,
shelfState: _shelfState, shelfState: _shelfState,
} };
CourseService.changeVideoShelfState(params).then((res) => { CourseService.changeVideoShelfState(params).then((res) => {
if (res.success) { if (res.success) {
if (_shelfState === 'YES') { if (_shelfState === 'YES') {
message.success('已开启展示') message.success('已开启展示');
} else { } else {
message.success('已取消展示') message.success('已取消展示');
} }
this.props.changeShelfState(index, _shelfState) this.props.changeShelfState(index, _shelfState);
} }
}) });
} };
// 删除视频课 // 删除视频课
handleDeleteGraphicsCourse = (scheduleId) => { handleDeleteGraphicsCourse = (scheduleId) => {
Modal.confirm({ Modal.confirm({
title: '你确定要删除此线上课吗?', title: '你确定要删除此线上课吗?',
content: '删除后,学员将不能进行观看。', content: '删除后,学员将不能进行观看。',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>, icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
okText: '确定', okText: '确定',
okType: 'danger', okType: 'danger',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
const param = { const param = {
courseId: scheduleId, courseId: scheduleId,
storeId: User.getStoreId(), storeId: User.getStoreId(),
} };
CourseService.delVideoSchedule(param).then(() => { CourseService.delVideoSchedule(param).then(() => {
message.success('删除成功') message.success('删除成功');
this.props.onChange() this.props.onChange();
}) });
}, },
}) });
} };
// 显示分享弹窗 // 显示分享弹窗
handleShowShareModal = (record, needStr = false) => { handleShowShareModal = (record, needStr = false) => {
const { id, scheduleVideoUrl } = record const { id, scheduleVideoUrl } = record;
const htmlUrl = `${LIVE_SHARE}graphics_detail/${id}?id=${User.getStoreId()}` const htmlUrl = `${LIVE_SHARE}graphics_detail/${id}?id=${User.getStoreId()}`;
const longUrl = htmlUrl const longUrl = htmlUrl;
const { coverUrl, courseName } = record const { coverUrl, courseName } = record;
const shareData = { const shareData = {
longUrl, longUrl,
coverUrl, coverUrl,
scheduleVideoUrl, scheduleVideoUrl,
courseName, courseName,
} };
const shareLiveModal = ( const shareLiveModal = (
<ShareLiveModal <ShareLiveModal
needStr={needStr} needStr={needStr}
data={shareData} data={shareData}
type='graphicsClass' type='graphicsClass'
title='图文课' title='图文课'
close={() => { close={() => {
this.setState({ this.setState({
shareLiveModal: null, shareLiveModal: null,
}) });
localStorage.setItem('videoCourseItem', '') localStorage.setItem('videoCourseItem', '');
}} }}
/> />
) );
this.setState({ shareLiveModal }) this.setState({ shareLiveModal });
} };
handleChangeTable = (pagination, filters, sorter) => { handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter const { columnKey, order } = sorter;
const { query } = this.props const { query } = this.props;
let { order: _order } = query let { order: _order } = query;
// 按创建时间升序排序 // 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') { if (columnKey === 'created' && order === 'ascend') {
_order = 'CREATED_ASC' _order = 'CREATED_ASC';
} }
// 按创建时间降序排序 // 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { if (columnKey === 'created' && order === 'descend') {
_order = 'CREATED_DESC' _order = 'CREATED_DESC';
} }
// 按更新时间升序排序 // 按更新时间升序排序
if (columnKey === 'updated' && order === 'ascend') { if (columnKey === 'updated' && order === 'ascend') {
_order = 'UPDATED_ASC' _order = 'UPDATED_ASC';
} }
// 按更新时间降序排序 // 按更新时间降序排序
if (columnKey === 'updated' && order === 'descend') { if (columnKey === 'updated' && order === 'descend') {
_order = 'UPDATED_DESC' _order = 'UPDATED_DESC';
} }
const _query = { const _query = {
...query, ...query,
orderEnum: _order, orderEnum: _order,
} };
this.props.onChange(_query) 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
const { current, size } = query
return ( closeRelatedPlanModalVisible = () => {
<div className='video-course-list'> this.setState({
<XMTable RelatedPlanModalVisible: false,
renderEmpty={{ });
image: college, };
description: '暂无数据', onChangeSelectPlanList = (selectPlanList) => {
}} this.setState({
rowKey={(record) => record.id} selectPlanList: selectPlanList,
dataSource={dataSource} });
columns={this.parseColumns()} };
onChange={this.handleChangeTable} onConfirmSelectPlanList = () => {
pagination={false} this.setState(
scroll={{ x: 1500 }} {
bordered RelatedPlanModalVisible: false,
className='video-list-table' },
/> () => {
this.props.onChange();
}
);
};
render() {
const { RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state;
const { dataSource = [], totalCount, query } = this.props;
const { current, size } = query;
<div className='box-footer'> return (
<PageControl <div className='video-course-list'>
current={current - 1} <XMTable
pageSize={size} renderEmpty={{
total={totalCount} image: college,
toPage={(page) => { description: '暂无数据',
const _query = { ...query, current: page + 1 } }}
this.props.onChange(_query) rowKey={(record) => record.id}
}} dataSource={dataSource}
/> columns={this.parseColumns()}
</div> onChange={this.handleChangeTable}
{RelatedPlanModalVisible && ( pagination={false}
<RelatedPlanModal scroll={{ x: 1500 }}
onClose={this.closeRelatedPlanModalVisible} bordered
visible={RelatedPlanModalVisible} className='video-list-table'
selectCourseId={selectCourseId} />
selectPlanList={selectPlanList}
onChange={this.onChangeSelectPlanList} <div className='box-footer'>
onConfirm={this.onConfirmSelectPlanList} <PageControl
/> current={current - 1}
)} pageSize={size}
{RelatedPlanModalVisible && ( total={totalCount}
<RelatedPlanModal toPage={(page) => {
onClose={this.closeRelatedPlanModalVisible} const _query = { ...query, current: page + 1 };
visible={RelatedPlanModalVisible} this.props.onChange(_query);
selectCourseId={selectCourseId} }}
selectPlanList={selectPlanList} />
onChange={this.onChangeSelectPlanList} </div>
onConfirm={this.onConfirmSelectPlanList} {RelatedPlanModalVisible && (
/> <RelatedPlanModal
)} onClose={this.closeRelatedPlanModalVisible}
{this.state.shareLiveModal} visible={RelatedPlanModalVisible}
{this.state.watchDataModal} selectCourseId={selectCourseId}
</div> selectPlanList={selectPlanList}
) onChange={this.onChangeSelectPlanList}
} onConfirm={this.onConfirmSelectPlanList}
/>
)}
{this.state.shareLiveModal}
{this.state.watchDataModal}
</div>
);
}
} }
export default GraphicsCourseList export default GraphicsCourseList;
...@@ -2,8 +2,8 @@ import User from '@/common/js/user'; ...@@ -2,8 +2,8 @@ import User from '@/common/js/user';
import college from '@/common/lottie/college'; import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components'; import { PageControl, XMTable } from '@/components';
import CourseService from '@/domains/course-domain/CourseService'; import CourseService from '@/domains/course-domain/CourseService';
import PlanService from '@/domains/plan-domain/planService'; import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
import { Input, Modal } from 'antd'; import { Input, Modal, Tooltip } from 'antd';
import React from 'react'; import React from 'react';
import _ from 'underscore'; import _ from 'underscore';
import './RelatedPlanModal.less'; import './RelatedPlanModal.less';
...@@ -33,7 +33,7 @@ class RelatedPlanModal extends React.Component { ...@@ -33,7 +33,7 @@ class RelatedPlanModal extends React.Component {
size, size,
storeId: User.getStoreId(), storeId: User.getStoreId(),
}; };
PlanService.getStorePlanAll(params).then((res) => { TaskCenterService.getStoreTaskAll(params).then((res) => {
const { result = {} } = res; const { result = {} } = res;
const { records = [], total = 0 } = result; const { records = [], total = 0 } = result;
this.setState({ this.setState({
...@@ -65,11 +65,31 @@ class RelatedPlanModal extends React.Component { ...@@ -65,11 +65,31 @@ class RelatedPlanModal extends React.Component {
); );
}; };
renderTitle = () => {
return (
<div className='tip-title'>
<p>为了不影响学员学习,系统已对数据进行筛选</p>
<p>1、课程不能直接关联「已发布」的培训任务筛选;</p>
<p>2、一个课程不能重复出现在同一培训任务中</p>
</div>
);
};
renderTableTitle = () => {
return (
<div>
<Tooltip title={this.renderTitle()} overlayClassName='table-title-tooltip'>
培训任务<span className='icon iconfont table-title'>&#xe6f2;</span>
</Tooltip>
</div>
);
};
// 请求表头 // 请求表头
parsePlanColumns = () => { parsePlanColumns = () => {
const columns = [ const columns = [
{ {
title: '培训计划', title: this.renderTableTitle(),
key: 'planName', key: 'planName',
dataIndex: 'planName', dataIndex: 'planName',
render: (val, record) => { render: (val, record) => {
...@@ -93,38 +113,37 @@ class RelatedPlanModal extends React.Component { ...@@ -93,38 +113,37 @@ class RelatedPlanModal extends React.Component {
]; ];
return columns; return columns;
}; };
selectPlanList = (record, selected, planId) => { selectPlanList = (record, selected, taskId) => {
const { selectPlanList } = this.props; const { selectPlanList } = this.props;
let _selectPlanList = { ...selectPlanList }; let _selectPlanList = { ...selectPlanList };
if (selected) { if (selected) {
if (!_selectPlanList[planId]) { if (!_selectPlanList[taskId]) {
_selectPlanList[planId] = {}; _selectPlanList[taskId] = {};
} }
_selectPlanList[planId].taskBaseVOList = []; _selectPlanList[taskId].taskBaseVOList = [];
_selectPlanList[planId].planId = planId; _selectPlanList[taskId].taskId = taskId;
_selectPlanList[planId].taskBaseVOList.push(record); _selectPlanList[taskId].taskBaseVOList.push({ ...record, stageId: record.taskId });
} else { } else {
if (!_selectPlanList[planId]) { if (!_selectPlanList[taskId]) {
_selectPlanList[planId] = {}; _selectPlanList[taskId] = {};
} }
_selectPlanList[planId].taskBaseVOList = []; _selectPlanList[taskId].taskBaseVOList = [];
_selectPlanList[planId].planId = planId; _selectPlanList[taskId].taskId = taskId;
} }
this.props.onChange(_selectPlanList); this.props.onChange(_selectPlanList);
// this.setState({selectPlanList:_selectPlanList});
}; };
handleSelectPlanListData(selectPlanList) { handleSelectPlanListData(selectPlanList) {
let _selectPlanList = []; let _selectPlanList = [];
for (let key in selectPlanList) { for (let key in selectPlanList) {
let item = {}; let item = {};
if (selectPlanList[key].taskBaseVOList) { if (selectPlanList[key].taskBaseVOList) {
item.planId = selectPlanList[key].planId; item.taskId = selectPlanList[key].taskId;
if (selectPlanList[key].taskBaseVOList[0]) { if (selectPlanList[key].taskBaseVOList[0]) {
item.taskId = selectPlanList[key].taskBaseVOList[0].taskId; item.stageId = selectPlanList[key].taskBaseVOList[0].stageId;
} }
} }
if (item.taskId) { if (item.stageId) {
_selectPlanList.push(item); _selectPlanList.push(item);
} }
} }
...@@ -134,10 +153,10 @@ class RelatedPlanModal extends React.Component { ...@@ -134,10 +153,10 @@ class RelatedPlanModal extends React.Component {
const { selectPlanList } = this.props; const { selectPlanList } = this.props;
const params = { const params = {
courseId: this.props.selectCourseId, courseId: this.props.selectCourseId,
relatedPlanList: this.handleSelectPlanListData(selectPlanList), relatedTaskList: this.handleSelectPlanListData(selectPlanList),
storeId: User.getStoreId(), storeId: User.getStoreId(),
}; };
CourseService.relatedCourseToPlan(params).then((res) => { TaskCenterService.relatedCourseToTask(params).then((res) => {
this.props.onConfirm(); this.props.onConfirm();
}); });
}; };
...@@ -159,7 +178,7 @@ class RelatedPlanModal extends React.Component { ...@@ -159,7 +178,7 @@ class RelatedPlanModal extends React.Component {
const { visible, selectPlanList } = this.props; const { visible, selectPlanList } = this.props;
return ( return (
<Modal <Modal
title='关联培训计划' title='关联培训任务'
onCancel={this.props.onClose} onCancel={this.props.onClose}
maskClosable={false} maskClosable={false}
visible={visible} visible={visible}
...@@ -170,7 +189,7 @@ class RelatedPlanModal extends React.Component { ...@@ -170,7 +189,7 @@ class RelatedPlanModal extends React.Component {
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}> closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}>
<div className='search-container'> <div className='search-container'>
<Search <Search
placeholder='搜索培训计划名称' placeholder='搜索培训任务名称'
style={{ width: 207 }} style={{ width: 207 }}
onChange={(e) => { onChange={(e) => {
this.handleChangePlanName(e.target.value); this.handleChangePlanName(e.target.value);
...@@ -200,7 +219,7 @@ class RelatedPlanModal extends React.Component { ...@@ -200,7 +219,7 @@ class RelatedPlanModal extends React.Component {
image: college, image: college,
description: '暂无数据', description: '暂无数据',
}} }}
rowKey={(record) => record.planId} rowKey={(record) => record.taskId}
className='plan-table' className='plan-table'
dataSource={dataSource} dataSource={dataSource}
columns={this.parsePlanColumns()} columns={this.parsePlanColumns()}
...@@ -210,14 +229,12 @@ class RelatedPlanModal extends React.Component { ...@@ -210,14 +229,12 @@ class RelatedPlanModal extends React.Component {
return; return;
} }
if (_record.taskBaseVOList.length !== 0) { if (_record.taskBaseVOList.length !== 0) {
const selectPlan = selectPlanList[_record.planId]; const selectPlan = selectPlanList[_record.taskId];
let taskBaseVOList = []; let taskBaseVOList = [];
if (selectPlan) { if (selectPlan) {
taskBaseVOList = selectPlan.taskBaseVOList; taskBaseVOList = selectPlan.taskBaseVOList;
} }
console.log('taskBaseVOList', taskBaseVOList);
return ( return (
<div> <div>
<XMTable <XMTable
...@@ -232,9 +249,9 @@ class RelatedPlanModal extends React.Component { ...@@ -232,9 +249,9 @@ class RelatedPlanModal extends React.Component {
className='child-table' className='child-table'
rowSelection={{ rowSelection={{
type: 'checkbox', type: 'checkbox',
selectedRowKeys: _.pluck(taskBaseVOList, 'taskId'), selectedRowKeys: _.pluck(taskBaseVOList, 'stageId'),
onSelect: (record, selected) => { onSelect: (record, selected) => {
this.selectPlanList(record, selected, _record.planId); this.selectPlanList(record, selected, _record.taskId);
}, },
onSelectAll: (selected, _selectedRows, changeRows) => {}, onSelectAll: (selected, _selectedRows, changeRows) => {},
}} }}
......
.related-plan-modal{ .table-title-tooltip {
.search-container{ max-width: 600px!important;
margin-bottom:16px; }
.related-plan-modal {
.table-title {
margin-left: 8px;
margin-left: 5px;
cursor: pointer;
color: rgb(191, 191, 191);
font-size: 14px;
font-weight: normal;
}
.search-container {
margin-bottom: 16px;
}
.select-container {
margin-bottom: 12px;
.con {
background: #e9efff;
border-radius: 4px;
padding: 6px 16px;
display: inline-flex;
align-items: center;
justify-content: space-between;
.tip {
font-size: 14px;
color: #2966ff;
margin-right: 8px;
}
.text {
font-size: 14px;
color: #666;
margin-right: 30px;
}
.clear {
color: #2966ff;
font-size: 14px;
}
} }
.select-container{ }
margin-bottom:12px; .plan-table {
.con{ .taskName {
background: #E9EFFF; color: #666666;
border-radius: 4px; font-size: 14px;
padding:6px 16px;
display: inline-flex;
align-items: center;
justify-content: space-between;
.tip{
font-size:14px;
color:#2966FF;
margin-right:8px;
}
.text{
font-size:14px;
color:#666;
margin-right:30px;
}
.clear{
color:#2966FF;
font-size:14px;
}
}
} }
.plan-table{ .task-learn-percentage {
.taskName{ color: #666666;
color:#666666; font-size: 14px;
font-size:14px; }
} .course-info {
.task-learn-percentage{ margin-left: 57px;
color:#666666; .course-type {
font-size:14px; font-size: 11px;
color: #666666;
padding: 0px 6px;
line-height: 16px;
border: 1px solid #999999;
margin-right: 4px;
border-radius: 2px;
}
.course-name {
color: #666666;
font-size: 14px;
margin-right: 8px;
}
.tip {
font-size: 14px;
color: #ff4f4f;
margin-right: 2px;
}
.course-state {
color: #999;
font-size: 14px;
}
}
.ant-table-content {
border: 1px solid #e8e8e8;
tr {
td {
border: none;
} }
.course-info{ .child-table {
margin-left:57px; .ant-table-content {
.course-type{ border: none;
font-size:11px; thead {
color:#666666; display: none;
padding:0px 6px;
line-height: 16px;
border: 1px solid #999999;
margin-right:4px;
border-radius: 2px;
}
.course-name{
color:#666666;
font-size:14px;
margin-right:8px;
} }
.tip{ tbody tr td {
font-size:14px; border-bottom: none;
color:#FF4F4F;
margin-right:2px;
}
.course-state{
color:#999;
font-size:14px;
} }
}
} }
.ant-table-content{ }
border:1px solid #e8e8e8; .odd-row {
tr{ background: transparent;
td{ td {
border:none; background: #fff;
} }
.child-table{ & + .ant-table-expanded-row {
.ant-table-content{ background: transparent;
border:none; td {
thead{ background: #fff;
display:none; }
} }
tbody tr td{ &:hover {
border-bottom:none; & + .ant-table-expanded-row {
} background: transparent;
} td {
background: #f3f6fa !important;
}
}
.odd-row{
background:transparent;
td{
background: #FFF;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FFF;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
}
}
} }
.even-row{ }
background:transparent; }
td{ }
background: #FAFAFA; .even-row {
} background: transparent;
& + .ant-table-expanded-row{ td {
background:transparent; background: #fafafa;
td{ }
background: #FAFAFA; & + .ant-table-expanded-row {
} background: transparent;
} td {
&:hover{ background: #fafafa;
& + .ant-table-expanded-row{ }
background:transparent; }
td{ &:hover {
background: #F3f6fa !important; & + .ant-table-expanded-row {
} background: transparent;
} td {
} background: #f3f6fa !important;
} }
}
} }
}
} }
} }
\ No newline at end of file }
import React from 'react' import React from 'react';
import { Modal, message, Tooltip, Switch, Dropdown } from 'antd' import { Modal, message, Tooltip, Switch, Dropdown } from 'antd';
import _ from 'underscore' import _ from 'underscore';
import { PageControl } from '@/components' import { PageControl } from '@/components';
import { LIVE_SHARE } from '@/domains/course-domain/constants' import { LIVE_SHARE } from '@/domains/course-domain/constants';
import { Route, withRouter } from 'react-router-dom' import { Route, withRouter } from 'react-router-dom';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal' import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import CourseService from '@/domains/course-domain/CourseService' import CourseService from '@/domains/course-domain/CourseService';
import RelatedPlanModal from '../../modal/RelatedPlanModal' import RelatedPlanModal from '../../modal/RelatedPlanModal';
import User from '@/common/js/user' import User from '@/common/js/user';
import VideoCourseDetail from '../VideoCourseDetail' import VideoCourseDetail from '../VideoCourseDetail';
import WatchData from './WatchData' import WatchData from './WatchData';
import { XMTable } from '@/components' import { XMTable } from '@/components';
import college from '@/common/lottie/college' import college from '@/common/lottie/college';
import './VideoCourseList.less' import './VideoCourseList.less';
class VideoCourseList extends React.Component { class VideoCourseList extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
id: '', // 视频课ID id: '', // 视频课ID
studentIds: [], studentIds: [],
...@@ -24,36 +24,36 @@ class VideoCourseList extends React.Component { ...@@ -24,36 +24,36 @@ class VideoCourseList extends React.Component {
selectPlanList: {}, selectPlanList: {},
ShelfLoading: false, ShelfLoading: false,
// dataSource: [], // dataSource: [],
} };
} }
componentDidMount() { componentDidMount() {
const videoCourseItem = localStorage.getItem('videoCourseItem') const videoCourseItem = localStorage.getItem('videoCourseItem');
if (videoCourseItem) { if (videoCourseItem) {
const _videoCourseItem = JSON.parse(videoCourseItem) const _videoCourseItem = JSON.parse(videoCourseItem);
this.handleShowShareModal(_videoCourseItem, true) this.handleShowShareModal(_videoCourseItem, true);
} }
} }
// 跳转课程详情页 // 跳转课程详情页
handleLinkToCourseDetail = (courseId) => { handleLinkToCourseDetail = (courseId) => {
const { match } = this.props const { match } = this.props;
window.RCHistory.push(`${match.url}/video-course-detail?courseId=${courseId}`) window.RCHistory.push(`${match.url}/video-course-detail?courseId=${courseId}`);
} };
// 观看数据弹窗 // 观看数据弹窗
handleShowWatchDataModal = (item) => { handleShowWatchDataModal = (item) => {
const { match } = this.props const { match } = this.props;
window.RCHistory.push({ window.RCHistory.push({
pathname: `${match.url}/course-data?courseName=${item.courseName}&courseId=${item.id}`, pathname: `${match.url}/course-data?courseName=${item.courseName}&courseId=${item.id}`,
}) });
} };
// 请求表头 // 请求表头
parseColumns = () => { parseColumns = () => {
const { type } = this.props const { type } = this.props;
const { ShelfLoading } = this.state const { ShelfLoading } = this.state;
const columns = [ const columns = [
{ {
title: '线上课', title: '线上课',
...@@ -62,7 +62,7 @@ class VideoCourseList extends React.Component { ...@@ -62,7 +62,7 @@ class VideoCourseList extends React.Component {
width: 321, width: 321,
fixed: 'left', fixed: 'left',
render: (val, record) => { render: (val, record) => {
const { coverUrl } = record const { coverUrl } = record;
return ( return (
<div className='record__item'> <div className='record__item'>
<img className='course-cover' src={coverUrl || 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'} alt='封面图' /> <img className='course-cover' src={coverUrl || 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'} alt='封面图' />
...@@ -77,7 +77,7 @@ class VideoCourseList extends React.Component { ...@@ -77,7 +77,7 @@ class VideoCourseList extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
...@@ -97,7 +97,7 @@ class VideoCourseList extends React.Component { ...@@ -97,7 +97,7 @@ class VideoCourseList extends React.Component {
dataIndex: 'categoryName', dataIndex: 'categoryName',
width: 150, width: 150,
render: (val, record) => { render: (val, record) => {
return <div className='record__item'>{record.categorySonName}</div> return <div className='record__item'>{record.categorySonName}</div>;
}, },
}, },
{ {
...@@ -108,7 +108,7 @@ class VideoCourseList extends React.Component { ...@@ -108,7 +108,7 @@ class VideoCourseList extends React.Component {
width: 100, width: 100,
align: 'right', align: 'right',
render: (val, item) => { render: (val, item) => {
return <div onClick={() => this.handleLinkToCourseDetail(item.id)}>{val || 1}</div> return <div onClick={() => this.handleLinkToCourseDetail(item.id)}>{val || 1}</div>;
}, },
}, },
{ {
...@@ -140,10 +140,10 @@ class VideoCourseList extends React.Component { ...@@ -140,10 +140,10 @@ class VideoCourseList extends React.Component {
checked={item.shelfState === 'YES'} checked={item.shelfState === 'YES'}
defaultChecked={item.shelfState} defaultChecked={item.shelfState}
onClick={(checked) => { onClick={(checked) => {
this.changeShelfState(checked, item, index) this.changeShelfState(checked, item, index);
}} }}
/> />
) );
}, },
}, },
{ {
...@@ -157,7 +157,7 @@ class VideoCourseList extends React.Component { ...@@ -157,7 +157,7 @@ class VideoCourseList extends React.Component {
<div className='watchUserCount' onClick={() => this.handleShowWatchDataModal(item)}> <div className='watchUserCount' onClick={() => this.handleShowWatchDataModal(item)}>
{val || 0} {val || 0}
</div> </div>
) );
}, },
}, },
{ {
...@@ -174,7 +174,7 @@ class VideoCourseList extends React.Component { ...@@ -174,7 +174,7 @@ class VideoCourseList extends React.Component {
</Tooltip> </Tooltip>
)} )}
</div> </div>
) );
}, },
}, },
{ {
...@@ -184,7 +184,7 @@ class VideoCourseList extends React.Component { ...@@ -184,7 +184,7 @@ class VideoCourseList extends React.Component {
dataIndex: 'created', dataIndex: 'created',
sorter: true, sorter: true,
render: (val) => { render: (val) => {
return window.formatDate('YYYY-MM-DD H:i', val) return window.formatDate('YYYY-MM-DD H:i', val);
}, },
}, },
{ {
...@@ -194,7 +194,7 @@ class VideoCourseList extends React.Component { ...@@ -194,7 +194,7 @@ class VideoCourseList extends React.Component {
dataIndex: 'updated', dataIndex: 'updated',
sorter: true, sorter: true,
render: (val) => { render: (val) => {
return window.formatDate('YYYY-MM-DD H:i', val) return window.formatDate('YYYY-MM-DD H:i', val);
}, },
}, },
{ {
...@@ -210,10 +210,10 @@ class VideoCourseList extends React.Component { ...@@ -210,10 +210,10 @@ class VideoCourseList extends React.Component {
<Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter> <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter>
{record.relatedPlanList.map((item, index) => { {record.relatedPlanList.map((item, index) => {
return ( return (
<span key={item.planId}> <span key={item.taskId}>
{item.planName} {index < record.relatedPlanList.length - 1 && <span></span>} {item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}
</span> </span>
) );
})} })}
</Tooltip> </Tooltip>
</When> </When>
...@@ -222,7 +222,7 @@ class VideoCourseList extends React.Component { ...@@ -222,7 +222,7 @@ class VideoCourseList extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
...@@ -249,17 +249,17 @@ class VideoCourseList extends React.Component { ...@@ -249,17 +249,17 @@ class VideoCourseList extends React.Component {
</span> </span>
</Dropdown> </Dropdown>
</div> </div>
) );
}, },
}, },
] ];
type !== 'internal' && columns.splice(5, 1) type !== 'internal' && columns.splice(5, 1);
return columns return columns;
} };
renderMoreOperate = (item) => { renderMoreOperate = (item) => {
const { type } = this.props const { type } = this.props;
return ( return (
<div className='live-course-more-menu'> <div className='live-course-more-menu'>
<If condition={type !== 'internal'}> <If condition={type !== 'internal'}>
...@@ -269,14 +269,14 @@ class VideoCourseList extends React.Component { ...@@ -269,14 +269,14 @@ class VideoCourseList extends React.Component {
</If> </If>
{(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && ( {(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<div className='operate__item' onClick={() => this.handleRelatedModalShow(item)}> <div className='operate__item' onClick={() => this.handleRelatedModalShow(item)}>
关联培训计划 关联培训任务
</div> </div>
)} )}
<If condition={type === 'internal'}> <If condition={type === 'internal'}>
<div <div
className='operate__item' className='operate__item'
onClick={() => { onClick={() => {
window.RCHistory.push(`/create-video-course?type=edit&id=${item.id}`) window.RCHistory.push(`/create-video-course?type=edit&id=${item.id}`);
}}> }}>
编辑 编辑
</div> </div>
...@@ -285,41 +285,41 @@ class VideoCourseList extends React.Component { ...@@ -285,41 +285,41 @@ class VideoCourseList extends React.Component {
</div> </div>
</If> </If>
</div> </div>
) );
} };
handlePlanName = (planArray) => { handlePlanName = (planArray) => {
let planStr = '' let planStr = '';
planArray.forEach((item, index) => { planArray.forEach((item, index) => {
if (index < planArray.length - 1) { if (index < planArray.length - 1) {
planStr = planStr + item.planName + '、' planStr = planStr + item.planName + '、';
} else { } else {
planStr = planStr + item.planName planStr = planStr + item.planName;
} }
}) });
return planStr return planStr;
} };
//改变上架状态 //改变上架状态
changeShelfState = (checked, item, index) => { changeShelfState = (checked, item, index) => {
let _shelfState = checked ? 'YES' : 'NO' let _shelfState = checked ? 'YES' : 'NO';
if (checked) { if (checked) {
_shelfState = 'YES' _shelfState = 'YES';
} else { } else {
_shelfState = 'NO' _shelfState = 'NO';
} }
const params = { const params = {
courseId: item.id, courseId: item.id,
shelfState: _shelfState, shelfState: _shelfState,
} };
CourseService.changeVideoShelfState(params).then(() => { CourseService.changeVideoShelfState(params).then(() => {
if (_shelfState === 'YES') { if (_shelfState === 'YES') {
message.success('已开启展示') message.success('已开启展示');
} else { } else {
message.success('已取消展示') message.success('已取消展示');
} }
this.props.changeShelfState(index, _shelfState) this.props.changeShelfState(index, _shelfState);
}) });
} };
// 删除线上课 // 删除线上课
handleDeleteVideoCourse = (scheduleId) => { handleDeleteVideoCourse = (scheduleId) => {
...@@ -334,28 +334,28 @@ class VideoCourseList extends React.Component { ...@@ -334,28 +334,28 @@ class VideoCourseList extends React.Component {
const param = { const param = {
courseId: scheduleId, courseId: scheduleId,
storeId: User.getStoreId(), storeId: User.getStoreId(),
} };
CourseService.delVideoSchedule(param).then(() => { CourseService.delVideoSchedule(param).then(() => {
message.success('删除成功') message.success('删除成功');
this.props.onChange() this.props.onChange();
}) });
}, },
}) });
} };
// 显示分享弹窗 // 显示分享弹窗
handleShowShareModal = (record, needStr = false) => { handleShowShareModal = (record, needStr = false) => {
const { type } = this.props const { type } = this.props;
const { id, scheduleVideoUrl } = record const { id, scheduleVideoUrl } = record;
const htmlUrl = `${LIVE_SHARE}video_detail/${id}?id=${User.getStoreId()}` const htmlUrl = `${LIVE_SHARE}video_detail/${id}?id=${User.getStoreId()}`;
const longUrl = htmlUrl const longUrl = htmlUrl;
const { coverUrl, courseName } = record const { coverUrl, courseName } = record;
const shareData = { const shareData = {
longUrl, longUrl,
coverUrl, coverUrl,
scheduleVideoUrl, scheduleVideoUrl,
courseName, courseName,
} };
const shareLiveModal = ( const shareLiveModal = (
<ShareLiveModal <ShareLiveModal
...@@ -367,82 +367,82 @@ class VideoCourseList extends React.Component { ...@@ -367,82 +367,82 @@ class VideoCourseList extends React.Component {
close={() => { close={() => {
this.setState({ this.setState({
shareLiveModal: null, shareLiveModal: null,
}) });
localStorage.setItem('videoCourseItem', '') localStorage.setItem('videoCourseItem', '');
}} }}
/> />
) );
this.setState({ shareLiveModal }) this.setState({ shareLiveModal });
} };
handleChangeTable = (pagination, filters, sorter) => { handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter const { columnKey, order } = sorter;
const { query } = this.props const { query } = this.props;
let { order: _order } = query let { order: _order } = query;
// 按创建时间升序排序 // 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') { if (columnKey === 'created' && order === 'ascend') {
_order = 'CREATED_ASC' _order = 'CREATED_ASC';
} }
// 按创建时间降序排序 // 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { if (columnKey === 'created' && order === 'descend') {
_order = 'CREATED_DESC' _order = 'CREATED_DESC';
} }
// 按更新时间升序排序 // 按更新时间升序排序
if (columnKey === 'updated' && order === 'ascend') { if (columnKey === 'updated' && order === 'ascend') {
_order = 'UPDATED_ASC' _order = 'UPDATED_ASC';
} }
// 按更新时间降序排序 // 按更新时间降序排序
if (columnKey === 'updated' && order === 'descend') { if (columnKey === 'updated' && order === 'descend') {
_order = 'UPDATED_DESC' _order = 'UPDATED_DESC';
} }
const _query = { const _query = {
...query, ...query,
orderEnum: _order, orderEnum: _order,
} };
this.props.onChange(_query) this.props.onChange(_query);
} };
handleRelatedModalShow = (item) => { handleRelatedModalShow = (item) => {
const selectPlanList = {} const selectPlanList = {};
if (item.relatedPlanList) { if (item.relatedPlanList) {
item.relatedPlanList.map((item, index) => { item.relatedPlanList.map((childItem, index) => {
selectPlanList[item.planId] = {} selectPlanList[childItem.taskId] = {};
selectPlanList[item.planId].planId = item.planId selectPlanList[childItem.taskId].taskId = childItem.taskId;
selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }] selectPlanList[childItem.taskId].taskBaseVOList = [{ stageId: childItem.stageId }];
return item return item;
}) });
} }
this.setState({ this.setState({
RelatedPlanModalVisible: true, RelatedPlanModalVisible: true,
selectCourseId: item.id, selectCourseId: item.id,
selectPlanList: selectPlanList, selectPlanList: selectPlanList,
}) });
} };
closeRelatedPlanModalVisible = () => { closeRelatedPlanModalVisible = () => {
this.setState({ this.setState({
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}) });
} };
onChangeSelectPlanList = (selectPlanList) => { onChangeSelectPlanList = (selectPlanList) => {
this.setState({ this.setState({
selectPlanList: selectPlanList, selectPlanList: selectPlanList,
}) });
} };
onConfirmSelectPlanList = () => { onConfirmSelectPlanList = () => {
this.setState( this.setState(
{ {
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}, },
() => { () => {
this.props.onChange() this.props.onChange();
} }
) );
} };
render() { render() {
const { dataSource = [], totalCount, query, type, match } = this.props const { dataSource = [], totalCount, query, type, match } = this.props;
const { current, size } = query const { current, size } = query;
const { RelatedPlanModalVisible, selectPlanList, selectCourseId } = this.state const { RelatedPlanModalVisible, selectPlanList, selectCourseId } = this.state;
return ( return (
<div className={`video-course-list ${type !== 'internal' ? 'video-course-list-mt' : ''}`}> <div className={`video-course-list ${type !== 'internal' ? 'video-course-list-mt' : ''}`}>
<XMTable <XMTable
...@@ -466,8 +466,8 @@ class VideoCourseList extends React.Component { ...@@ -466,8 +466,8 @@ class VideoCourseList extends React.Component {
pageSize={size} pageSize={size}
total={totalCount} total={totalCount}
toPage={(page) => { toPage={(page) => {
const _query = { ...query, current: page + 1 } const _query = { ...query, current: page + 1 };
this.props.onChange(_query) this.props.onChange(_query);
}} }}
/> />
</div> </div>
...@@ -486,8 +486,8 @@ class VideoCourseList extends React.Component { ...@@ -486,8 +486,8 @@ class VideoCourseList extends React.Component {
<Route path={`${match.url}/video-course-detail`} component={VideoCourseDetail} /> <Route path={`${match.url}/video-course-detail`} component={VideoCourseDetail} />
<Route path={`${match.url}/course-data`} component={WatchData} /> <Route path={`${match.url}/course-data`} component={WatchData} />
</div> </div>
) );
} }
} }
export default withRouter(VideoCourseList) export default withRouter(VideoCourseList);
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react';
import { Select, Tooltip, Carousel, Popover } from 'antd' import { Select, Tooltip, Carousel, Popover } from 'antd';
import DataSet from '@antv/data-set' import DataSet from '@antv/data-set';
import { Chart as G2Chart } from '@antv/g2' 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 { G2, Chart, Geom, Axis, Tooltip as G2Tooltip, Coord, Label, Legend, View, Guide, Shape, Facet, Util } from 'bizcharts';
import moment from 'moment' import moment from 'moment';
import Service from '@/common/js/service' import Service from '@/common/js/service';
import User from '@/common/js/user' import User from '@/common/js/user';
import HomeTip from './HomeTip' import HomeTip from './HomeTip';
import './Home.less' import './Home.less';
const Option = Select.Option const Option = Select.Option;
class Home extends React.Component { class Home extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
scheduleType: 'LIVE', scheduleType: 'LIVE',
list: [], list: [],
...@@ -34,54 +34,54 @@ class Home extends React.Component { ...@@ -34,54 +34,54 @@ class Home extends React.Component {
incWeekVisitCustomerNum: 0, incWeekVisitCustomerNum: 0,
courseNum: 0, //课程总数 courseNum: 0, //课程总数
inCourseNum: 0, //本月新增课程总数 inCourseNum: 0, //本月新增课程总数
} };
this._chart = null this._chart = null;
} }
componentDidMount() { componentDidMount() {
this.getPanelInfo() this.getPanelInfo();
this.getStudyInfo() this.getStudyInfo();
this.getHotCourse() this.getHotCourse();
this.getTrainingInfo() this.getTrainingInfo();
} }
getTrainingInfo() { getTrainingInfo() {
Service.Hades('public/hades/planOverview', { storeId: User.getStoreId() }).then((res) => { Service.Hades('public/hades/planOverview', { storeId: User.getStoreId() }).then((res) => {
if (res.success) { if (res.success) {
this.setState(res.result) this.setState(res.result);
} }
}) });
} }
getHotCourse() { getHotCourse() {
const { timeRange, scheduleType } = this.state const { timeRange, scheduleType } = this.state;
const data = { const data = {
hotNum: 5, hotNum: 5,
scheduleType, scheduleType,
storeId: User.getStoreId(), storeId: User.getStoreId(),
timeRange, timeRange,
} };
Service.Hades('public/courseCloud/hotCourse', data).then((res) => { Service.Hades('public/courseCloud/hotCourse', data).then((res) => {
if (res.success) { if (res.success) {
this.setState({ this.setState({
list: res.result, list: res.result,
}) });
} }
}) });
} }
getStudyInfo() { getStudyInfo() {
const { studyTimeRange } = this.state const { studyTimeRange } = this.state;
Service.Hades('public/hades/studyInfo', { storeId: User.getStoreId(), timeRange: studyTimeRange }).then((res) => { Service.Hades('public/hades/studyInfo', { storeId: User.getStoreId(), timeRange: studyTimeRange }).then((res) => {
if (res.success) { if (res.success) {
const dataList = res.result.map((item) => ({ const dataList = res.result.map((item) => ({
time: moment(item.dateline).format('MM-DD'), time: moment(item.dateline).format('MM-DD'),
studyNum: item.studyNum, studyNum: item.studyNum,
studyTime: Math.round(item.studyTime / 6) / 10, studyTime: Math.round(item.studyTime / 6) / 10,
})) }));
this.createChart(dataList) this.createChart(dataList);
} }
}) });
} }
getPanelInfo() { getPanelInfo() {
...@@ -106,22 +106,22 @@ class Home extends React.Component { ...@@ -106,22 +106,22 @@ class Home extends React.Component {
incWeekVisitCustomerNum: res.result.incWeekVisitCustomerNum, incWeekVisitCustomerNum: res.result.incWeekVisitCustomerNum,
courseNum: res.result.videoCourseNum + res.result.liveCourseNum + res.result.pictureCourseNum + res.result.offlineCourseNum, courseNum: res.result.videoCourseNum + res.result.liveCourseNum + res.result.pictureCourseNum + res.result.offlineCourseNum,
inCourseNum: res.result.incLiveCourseNum + res.result.incVideoCourseNum + res.result.incPictureCourseNum + res.result.incOfflineCourseNum, inCourseNum: res.result.incLiveCourseNum + res.result.incVideoCourseNum + res.result.incPictureCourseNum + res.result.incOfflineCourseNum,
}) });
} }
}) });
} }
showNumber(index) { showNumber(index) {
switch (index) { switch (index) {
case 0: case 0:
return 'https://image.xiaomaiketang.com/xm/D64QhNn74S.png' return 'https://image.xiaomaiketang.com/xm/D64QhNn74S.png';
case 1: case 1:
return 'https://image.xiaomaiketang.com/xm/Qfib4mnGJT.png' return 'https://image.xiaomaiketang.com/xm/Qfib4mnGJT.png';
case 2: case 2:
return 'https://image.xiaomaiketang.com/xm/8jKXHyrDaG.png' return 'https://image.xiaomaiketang.com/xm/8jKXHyrDaG.png';
default: default:
return 'https://image.xiaomaiketang.com/xm/D64QhNn74S.png' return 'https://image.xiaomaiketang.com/xm/D64QhNn74S.png';
} }
} }
...@@ -132,28 +132,28 @@ class Home extends React.Component { ...@@ -132,28 +132,28 @@ class Home extends React.Component {
forceFit: true, forceFit: true,
height: 290, height: 290,
padding: [48, 64], padding: [48, 64],
}) });
} }
this._chart.clear() this._chart.clear();
this._chart.source(data, { this._chart.source(data, {
studyTime: { studyTime: {
formatter: (val) => { formatter: (val) => {
return val return val;
}, },
tickCount: 5, tickCount: 5,
}, },
time: { time: {
formatter: (val) => { formatter: (val) => {
return `${val}` return `${val}`;
}, },
}, },
studyNum: { studyNum: {
formatter: (val) => { formatter: (val) => {
return val return val;
}, },
tickCount: 5, tickCount: 5,
}, },
}) });
this._chart.axis('time', { this._chart.axis('time', {
label: { label: {
offset: 20, offset: 20,
...@@ -168,7 +168,7 @@ class Home extends React.Component { ...@@ -168,7 +168,7 @@ class Home extends React.Component {
tickLine: { tickLine: {
stroke: '#E8E8E8', stroke: '#E8E8E8',
}, },
}) });
this._chart.axis('submitCount', { this._chart.axis('submitCount', {
label: { label: {
textStyle: { textStyle: {
...@@ -176,7 +176,7 @@ class Home extends React.Component { ...@@ -176,7 +176,7 @@ class Home extends React.Component {
fontSize: 14, fontSize: 14,
}, },
}, },
}) });
this._chart.axis('studyTime', { this._chart.axis('studyTime', {
label: { label: {
textStyle: { textStyle: {
...@@ -184,7 +184,7 @@ class Home extends React.Component { ...@@ -184,7 +184,7 @@ class Home extends React.Component {
fontSize: 14, fontSize: 14,
}, },
}, },
}) });
this._chart this._chart
.line() .line()
.position('time*studyNum') .position('time*studyNum')
...@@ -193,8 +193,8 @@ class Home extends React.Component { ...@@ -193,8 +193,8 @@ class Home extends React.Component {
return { return {
name: '学习人数', name: '学习人数',
value: studyNum + '人', value: studyNum + '人',
} };
}) });
this._chart this._chart
.line() .line()
.position('time*studyTime') .position('time*studyTime')
...@@ -203,10 +203,10 @@ class Home extends React.Component { ...@@ -203,10 +203,10 @@ class Home extends React.Component {
return { return {
name: '人均学习时长', name: '人均学习时长',
value: studyTime + '分钟', value: studyTime + '分钟',
} };
}) });
this._chart.legend(false) this._chart.legend(false);
this._chart.tooltip({ this._chart.tooltip({
containerTpl: containerTpl:
'<div class="g2-tooltip" style="background: #fff !important;">' + '<div class="g2-tooltip" style="background: #fff !important;">' +
...@@ -214,9 +214,9 @@ class Home extends React.Component { ...@@ -214,9 +214,9 @@ class Home extends React.Component {
'<ul class="g2-tooltip-list"></ul></div>', // tooltip 容器模板 '<ul class="g2-tooltip-list"></ul></div>', // tooltip 容器模板
itemTpl: itemTpl:
'<li data-index={index}><span style="background-color:{color};width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:8px;"></span>{name}<span style="display: inline-block; float: right; margin-left: 30px;">{value}</span></li>', // tooltip 每项记录的默认模板 '<li data-index={index}><span style="background-color:{color};width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:8px;"></span>{name}<span style="display: inline-block; float: right; margin-left: 30px;">{value}</span></li>', // tooltip 每项记录的默认模板
}) });
this._chart.render() this._chart.render();
} };
render() { render() {
const { const {
...@@ -245,7 +245,7 @@ class Home extends React.Component { ...@@ -245,7 +245,7 @@ class Home extends React.Component {
offlineCourseNum, offlineCourseNum,
weekVisitCustomerNum, weekVisitCustomerNum,
incWeekVisitCustomerNum, incWeekVisitCustomerNum,
} = this.state } = this.state;
const data = [ const data = [
{ {
item: '已完成培训', item: '已完成培训',
...@@ -255,26 +255,26 @@ class Home extends React.Component { ...@@ -255,26 +255,26 @@ class Home extends React.Component {
item: '未完成培训', item: '未完成培训',
count: unfinishedNum, count: unfinishedNum,
}, },
] ];
const { DataView } = DataSet const { DataView } = DataSet;
const { Html } = Guide const { Html } = Guide;
const sum = data[0].count + data[1].count const sum = data[0].count + data[1].count;
const dv = new DataView() const dv = new DataView();
sum && sum &&
dv.source(data).transform({ dv.source(data).transform({
type: 'percent', type: 'percent',
field: 'count', field: 'count',
dimension: 'item', dimension: 'item',
as: 'percent', as: 'percent',
}) });
const cols = { const cols = {
percent: { percent: {
formatter: (val) => { formatter: (val) => {
val = val * 100 + '%' val = val * 100 + '%';
return val return val;
}, },
}, },
} };
return ( return (
<div className='home-page'> <div className='home-page'>
<HomeTip /> <HomeTip />
...@@ -357,7 +357,7 @@ class Home extends React.Component { ...@@ -357,7 +357,7 @@ class Home extends React.Component {
<div className='data-item'> <div className='data-item'>
<div className='header'> <div className='header'>
<img className='header-icon' src='https://image.xiaomaiketang.com/xm/jZf3GNY5tY.png' alt='' /> <img className='header-icon' src='https://image.xiaomaiketang.com/xm/jZf3GNY5tY.png' alt='' />
<span className='header-word'>培训计划总数 (个)</span> <span className='header-word'>培训任务总数 (个)</span>
</div> </div>
<div className='data-number'>{trainingPlanNum}</div> <div className='data-number'>{trainingPlanNum}</div>
<div className='data-footer'> <div className='data-footer'>
...@@ -416,7 +416,7 @@ class Home extends React.Component { ...@@ -416,7 +416,7 @@ class Home extends React.Component {
style={{ width: 88 }} style={{ width: 88 }}
value={timeRange} value={timeRange}
onChange={(value) => { onChange={(value) => {
this.setState({ timeRange: value }, () => this.getHotCourse()) this.setState({ timeRange: value }, () => this.getHotCourse());
}}> }}>
<Option value='7'>近7天</Option> <Option value='7'>近7天</Option>
<Option value='15'>近15天</Option> <Option value='15'>近15天</Option>
...@@ -459,8 +459,8 @@ class Home extends React.Component { ...@@ -459,8 +459,8 @@ class Home extends React.Component {
</div> </div>
<div className='study-item'> <div className='study-item'>
<div className='study-title'> <div className='study-title'>
培训计划完成情况 培训任务完成情况
<Tooltip overlayClassName='data-plan-tooltip' title='若某人加入多个培训计划,则需完成所有已加入的培训计划后,才视为已完成培训'> <Tooltip overlayClassName='data-plan-tooltip' title='若某人加入多个培训任务,则需完成所有已加入的培训任务后,才视为已完成培训'>
<span className='iconfont icon'>&#xe61d;</span> <span className='iconfont icon'>&#xe61d;</span>
</Tooltip> </Tooltip>
<span className='tip'>(本月)</span> <span className='tip'>(本月)</span>
...@@ -479,7 +479,7 @@ class Home extends React.Component { ...@@ -479,7 +479,7 @@ class Home extends React.Component {
<Axis name='percent' /> <Axis name='percent' />
<G2Tooltip <G2Tooltip
showTitle={false} showTitle={false}
itemTpl='<li><span style="background-color:{color};" class="g2-tooltip-marker"></span>{name}: {value}</li>' itemTpl='<li><span style="background-color:{color};" class="g2-tooltip-marker"></span><span style="margin-right:16px;">{percent}</span><span>{num}</span></li>'
/> />
<Guide> <Guide>
<Html <Html
...@@ -492,15 +492,15 @@ class Home extends React.Component { ...@@ -492,15 +492,15 @@ class Home extends React.Component {
<Geom <Geom
type='intervalStack' type='intervalStack'
position='percent' position='percent'
color={['item', ['#FFBB54', '#2966FF']]} color={['item', ['#14CCA7', '#2966FF']]}
tooltip={[ tooltip={[
'item*percent', 'item*percent',
(item, percent) => { (item, percent) => {
percent = Math.round(percent * 100) + '%' percent = Math.round(percent * 100) + '%';
return { return {
name: item, num: item === '未完成培训' ? unfinishedNum + '人' : completeNum + '人',
value: percent, percent: percent,
} };
}, },
]} ]}
style={{ style={{
...@@ -526,15 +526,15 @@ class Home extends React.Component { ...@@ -526,15 +526,15 @@ class Home extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
<div className='circle-tip unfinished'> <div className='circle-tip finished'>
<div className='spot'></div> <div className='spot'></div>
<div className='number'>{unfinishedNum}</div> {/* <div className='number'>{completeNum}人</div> */}
<div className='word'>完成培训</div> <div className='word'>完成培训</div>
</div> </div>
<div className='circle-tip finished'> <div className='circle-tip unfinished'>
<div className='spot'></div> <div className='spot'></div>
<div className='number'>{completeNum}</div> {/* <div className='number'>{unfinishedNum}人</div> */}
<div className='word'>完成培训</div> <div className='word'>完成培训</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -554,7 +554,7 @@ class Home extends React.Component { ...@@ -554,7 +554,7 @@ class Home extends React.Component {
style={{ width: 88 }} style={{ width: 88 }}
value={studyTimeRange} value={studyTimeRange}
onChange={(value) => { onChange={(value) => {
this.setState({ studyTimeRange: value }, () => this.getStudyInfo()) this.setState({ studyTimeRange: value }, () => this.getStudyInfo());
}}> }}>
<Option value='7'>近7天</Option> <Option value='7'>近7天</Option>
<Option value='15'>近15天</Option> <Option value='15'>近15天</Option>
...@@ -582,8 +582,8 @@ class Home extends React.Component { ...@@ -582,8 +582,8 @@ class Home extends React.Component {
</div> </div>
</div> </div>
</div> </div>
) );
} }
} }
export default Home export default Home;
...@@ -280,18 +280,19 @@ ...@@ -280,18 +280,19 @@
} }
.circle-tip { .circle-tip {
position: absolute; position: absolute;
left: 70%;
padding-left: 16px; padding-left: 16px;
&.unfinished { &.unfinished {
top: 152px; bottom: 2%;
left: 36%;
.spot { .spot {
background: #2966ff; background: #2966ff;
} }
} }
&.finished { &.finished {
top: 232px; bottom: 2%;
left: 18%;
.spot { .spot {
background: #ffbb54; background: #14cca7;
} }
} }
.spot { .spot {
...@@ -299,7 +300,7 @@ ...@@ -299,7 +300,7 @@
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 4px; border-radius: 4px;
top: 20px; top: 8px;
left: 0; left: 0;
} }
.number { .number {
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
* @Description: * @Description:
* @Author: zangsuyun * @Author: zangsuyun
* @Date: 2021-03-12 14:49:40 * @Date: 2021-03-12 14:49:40
* @LastEditors: yuananting * @LastEditors: wufan
* @LastEditTime: 2021-07-15 14:07:42 * @LastEditTime: 2021-08-15 18:31:16
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @Author: zangsuyun * @Author: zangsuyun
* @Date: 2021-03-13 11:48:24 * @Date: 2021-03-13 11:48:24
* @LastEditors: wufan * @LastEditors: wufan
* @LastEditTime: 2021-05-30 10:08:09 * @LastEditTime: 2021-08-15 18:31:21
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
......
...@@ -72,11 +72,19 @@ class BasicInfo extends React.Component { ...@@ -72,11 +72,19 @@ class BasicInfo extends React.Component {
this.props.onChange('coverId', null); this.props.onChange('coverId', null);
}, 1000); }, 1000);
}; };
handleSelectCover = (file) => { handleSelectCover = (file) => {
this.setState({
visible: true, if(file){
imageFile: file, this.setState({
}); visible: true,
imageFile: file,
});
}else{
message.warning('请选择文件!')
}
}; };
//获取resourceId //获取resourceId
getSignature = (blob, fileName) => { getSignature = (blob, fileName) => {
......
...@@ -6,66 +6,66 @@ ...@@ -6,66 +6,66 @@
* @Description: 大班直播分享弹窗 * @Description: 大班直播分享弹窗
*/ */
import React from 'react' import React from 'react';
import { Modal, Button, message } from 'antd' import { Modal, Button, message } from 'antd';
import domtoimage from 'dom-to-image' import domtoimage from 'dom-to-image';
import qrcode from '@/libs/qrcode/qrcode.js' import qrcode from '@/libs/qrcode/qrcode.js';
import User from '@/common/js/user' import User from '@/common/js/user';
import $ from 'jquery' import $ from 'jquery';
import CourseService from '@/domains/course-domain/CourseService' import CourseService from '@/domains/course-domain/CourseService';
import './SharePlanModal.less' import './SharePlanModal.less';
const DEFAULT_COVER = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png' const DEFAULT_COVER = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
class ShareLiveModal extends React.Component { class ShareLiveModal extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
shareUrl: '', shareUrl: '',
} };
} }
componentDidMount() { componentDidMount() {
// 获取短链接 // 获取短链接
this.handleConvertShortUrl() this.handleConvertShortUrl();
} }
handleConvertShortUrl = () => { handleConvertShortUrl = () => {
const { longUrl } = this.props.data const { longUrl } = this.props.data;
// 发请求 // 发请求
CourseService.getQrcode({ CourseService.getQrcode({
urls: [longUrl], urls: [longUrl],
}).then((res) => { }).then((res) => {
const { result = [] } = res const { result = [] } = res;
this.setState( this.setState(
{ {
shareUrl: result[0].shortUrl, shareUrl: result[0].shortUrl,
}, },
() => { () => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap') const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new qrcode({ const qrcodeNode = new qrcode({
text: this.state.shareUrl, text: this.state.shareUrl,
size: 98, size: 98,
}) });
qrcodeWrapDom.appendChild(qrcodeNode) qrcodeWrapDom.appendChild(qrcodeNode);
const qrcodeWrapDomDownload = document.querySelector('#qrcodeWrap-dowload') const qrcodeWrapDomDownload = document.querySelector('#qrcodeWrap-dowload');
const qrcodeNodeDownLoad = new qrcode({ const qrcodeNodeDownLoad = new qrcode({
text: this.state.shareUrl, text: this.state.shareUrl,
size: 196, size: 196,
}) });
qrcodeWrapDomDownload.appendChild(qrcodeNodeDownLoad) qrcodeWrapDomDownload.appendChild(qrcodeNodeDownLoad);
} }
) );
}) });
} };
componentWillUnmount() { componentWillUnmount() {
// 页面销毁之前清空定时器 // 页面销毁之前清空定时器
clearTimeout(this.timer) clearTimeout(this.timer);
} }
// 下载海报 // 下载海报
...@@ -77,29 +77,29 @@ class ShareLiveModal extends React.Component { ...@@ -77,29 +77,29 @@ class ShareLiveModal extends React.Component {
}, },
() => { () => {
this.setState({ time: new Date().valueOf() }, () => { this.setState({ time: new Date().valueOf() }, () => {
let node = document.getElementById('poster-dowload') let node = document.getElementById('poster-dowload');
domtoimage.toPng(node).then((imgData) => { domtoimage.toPng(node).then((imgData) => {
const download = document.createElement('a') const download = document.createElement('a');
const { planName } = this.props.data const { planName } = this.props.data;
$(download).attr('href', imgData).attr('download', `${planName}.png`).get(0).click() $(download).attr('href', imgData).attr('download', `${planName}.png`).get(0).click();
}) });
}) });
} }
) );
} };
// 复制分享链接 // 复制分享链接
handleCopy = () => { handleCopy = () => {
const textContent = document.getElementById('shareUrl').innerText const textContent = document.getElementById('shareUrl').innerText;
window.copyText(textContent) window.copyText(textContent);
message.success('复制成功!') message.success('复制成功!');
} };
render() { render() {
const { data } = this.props const { data } = this.props;
const { planName, coverUrl = DEFAULT_COVER } = data const { planName, coverUrl = DEFAULT_COVER } = data;
const { shareUrl, showImg, time } = this.state const { shareUrl, showImg, time } = this.state;
return ( return (
<Modal <Modal
title={'分享培训计划'} title={'分享培训计划'}
...@@ -185,8 +185,8 @@ class ShareLiveModal extends React.Component { ...@@ -185,8 +185,8 @@ class ShareLiveModal extends React.Component {
</div> </div>
</div> </div>
</Modal> </Modal>
) );
} }
} }
export default ShareLiveModal export default ShareLiveModal;
...@@ -71,8 +71,10 @@ class FolderList extends React.Component { ...@@ -71,8 +71,10 @@ class FolderList extends React.Component {
handleSelect = (folder) => { handleSelect = (folder) => {
// 只有文件才有预览功能 // 只有文件才有预览功能
if (folder.folderType === 'FOLDER') { if (folder.folderType === 'FOLDER') {
console.log(1)
this.handleSelectFolder(folder) this.handleSelectFolder(folder)
} else { } else {
console.log(2)
this.handleScanFile(folder) this.handleScanFile(folder)
} }
} }
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2019-09-10 18:26:03 * @Date: 2019-09-10 18:26:03
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-08-11 20:26:53 * @LastEditTime: 2021-09-03 10:32:44
* @Description: * @Description:
*/ */
import React, { useRef, useContext, useEffect, useState } from 'react'; import React, { useRef, useContext, useEffect, useState } from 'react';
...@@ -81,6 +81,7 @@ function Header(props) { ...@@ -81,6 +81,7 @@ function Header(props) {
setPhone(phone); setPhone(phone);
setAvatar(res.result.avatar); setAvatar(res.result.avatar);
User.setAvatar(res.result.avatar); User.setAvatar(res.result.avatar);
User.setStoreUserName(weChatAccount);
}); });
} }
...@@ -137,7 +138,7 @@ function Header(props) { ...@@ -137,7 +138,7 @@ function Header(props) {
<Tooltip title={nickName}> <Tooltip title={nickName}>
<div className='name'> <div className='name'>
{/* <span>{nickName}</span> */} {/* <span>{nickName}</span> */}
<WWOpenDataCom type="userName" openid={nickName}/> <WWOpenDataCom type='userName' openid={nickName} />
</div> </div>
</Tooltip> </Tooltip>
<span className='phone'>{phone}</span> <span className='phone'>{phone}</span>
...@@ -384,7 +385,7 @@ function Header(props) { ...@@ -384,7 +385,7 @@ function Header(props) {
/> />
<span className='name'> <span className='name'>
{/* {nickName} */} {/* {nickName} */}
<WWOpenDataCom type="userName" openid={nickName}/> <WWOpenDataCom type='userName' openid={nickName} />
</span> </span>
</div> </div>
</Dropdown> </Dropdown>
......
/* /*
* @Author: wufan * @Author: wufan
* @Date: 2020-11-27 16:21:49 * @Date: 2020-11-27 16:21:49
* @LastEditors: Please set LastEditors * @LastEditors: yuananting
* @LastEditTime: 2021-08-09 11:55:47 * @LastEditTime: 2021-08-09 19:20:49
* @Description: Description * @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from 'react';
import { import { Modal, Form, Button, Input, InputNumber, Radio, Row, Col, message, Tooltip } from 'antd';
Modal, import _, { values } from 'underscore';
Form, import './EmployeeAddOrEditModal.less';
Button, import { CropperModal } from '@/components/';
Input, import StoreService from '@/domains/store-domain/storeService';
InputNumber,
Radio,
Row,
Col,
message,
Tooltip,
} from "antd";
import _, { values } from "underscore";
import "./EmployeeAddOrEditModal.less";
import { CropperModal } from "@/components/";
import StoreService from "@/domains/store-domain/storeService";
import WWOpenDataCom from '@/components/WWOpenDataCom'; import WWOpenDataCom from '@/components/WWOpenDataCom';
import $ from "jquery"; import $ from 'jquery';
import User from "@/common/js/user"; import User from '@/common/js/user';
const RadioGroup = Radio.Group; const RadioGroup = Radio.Group;
declare let window: any; declare let window: any;
...@@ -44,37 +33,28 @@ interface AddEmployeeModalProps { ...@@ -44,37 +33,28 @@ interface AddEmployeeModalProps {
isWorkWechat: boolean; isWorkWechat: boolean;
} }
function AddEmployeeModal(props: AddEmployeeModalProps) { function AddEmployeeModal(props: AddEmployeeModalProps) {
const [nickName, setName] = useState(""); const [nickName, setName] = useState('');
const [phone, setPhone] = useState(""); const [phone, setPhone] = useState('');
const [role, setRole] = useState("CloudOperator"); const [role, setRole] = useState('CloudLecturer');
const [avatar, setAvatar] = useState( const [avatar, setAvatar] = useState('https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png');
"https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png"
);
const storeUserId = props.choosedItem.storeUserId; const storeUserId = props.choosedItem.storeUserId;
const [imgUrl, setImgUrl] = useState( const [imgUrl, setImgUrl] = useState(avatar || 'https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png');
avatar || "https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png" const [nameErrorMsg, setNameErrorMsg] = useState('');
); const [nameStatus, setNameStatus] = useState<'' | 'error' | 'success' | 'warning' | 'validating' | undefined>();
const [nameErrorMsg, setNameErrorMsg] = useState(""); const [phoneErrorMessage, setPhoneErrorMessage] = useState('');
const [nameStatus, setNameStatus] = useState< const [phoneStatus, setPhoneStatus] = useState<'' | 'error' | 'success' | 'warning' | 'validating' | undefined>();
"" | "error" | "success" | "warning" | "validating" | undefined
>();
const [phoneErrorMessage, setPhoneErrorMessage] = useState("");
const [phoneStatus, setPhoneStatus] = useState<
"" | "error" | "success" | "warning" | "validating" | undefined
>();
const [cropperModalVisible, setCropperModalVisible] = useState(false); const [cropperModalVisible, setCropperModalVisible] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
if (props.choosedItem.nickName) { if (props.choosedItem.nickName) {
console.log("props.choosedItem", props.choosedItem); console.log('props.choosedItem', props.choosedItem);
setName(props.choosedItem.nickName); setName(props.choosedItem.nickName);
console.log('choosedItem',props.choosedItem); console.log('choosedItem', props.choosedItem);
props.choosedItem.phone && setPhone(props.choosedItem.phone); props.choosedItem.phone && setPhone(props.choosedItem.phone);
props.choosedItem.role && setRole(props.choosedItem.role[0]); props.choosedItem.role && setRole(props.choosedItem.role[0]);
props.choosedItem.avatar && setAvatar(props.choosedItem.avatar); props.choosedItem.avatar && setAvatar(props.choosedItem.avatar);
const _role = props.choosedItem.role[0]; const _role = props.choosedItem.role[0];
form.setFieldsValue({ form.setFieldsValue({
nickName: props.choosedItem.nickName, nickName: props.choosedItem.nickName,
role: _role, role: _role,
...@@ -96,13 +76,13 @@ function AddEmployeeModal(props: AddEmployeeModalProps) { ...@@ -96,13 +76,13 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
function _handleUpdateAvatar(e: any): any { function _handleUpdateAvatar(e: any): any {
const avatar = e.target.files[0]; const avatar = e.target.files[0];
const newUrl = URL.createObjectURL(avatar); const newUrl = URL.createObjectURL(avatar);
const $image = $("#image"); const $image = $('#image');
setImgUrl(newUrl); setImgUrl(newUrl);
setCropperModalVisible(true); setCropperModalVisible(true);
} }
function _onUpload(): any { function _onUpload(): any {
$("#CrpperAvatarPic").trigger("click"); $('#CrpperAvatarPic').trigger('click');
} }
function changeAvatar(img: string): any { function changeAvatar(img: string): any {
...@@ -120,47 +100,47 @@ function AddEmployeeModal(props: AddEmployeeModalProps) { ...@@ -120,47 +100,47 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
function handleOk(): void { function handleOk(): void {
const values = form.getFieldsValue(); const values = form.getFieldsValue();
if (!values.nickName.trim()) { if (!values.nickName.trim()) {
setNameErrorMsg("请输入员工昵称"); setNameErrorMsg('请输入员工昵称');
setNameStatus("error"); setNameStatus('error');
return; return;
} }
setNameErrorMsg(""); setNameErrorMsg('');
setNameStatus(""); setNameStatus('');
if (!String(values.phone) && !props.isWorkWechat) { if (!String(values.phone) && !props.isWorkWechat) {
setPhoneErrorMessage("请输入手机号码"); setPhoneErrorMessage('请输入手机号码');
setPhoneStatus("error"); setPhoneStatus('error');
return; return;
} }
setPhoneErrorMessage(""); setPhoneErrorMessage('');
setPhoneStatus(""); setPhoneStatus('');
if (String(values.phone).length !== 11 && !props.isWorkWechat) { if (String(values.phone).length !== 11 && !props.isWorkWechat) {
setPhoneErrorMessage("请输入11位手机号"); setPhoneErrorMessage('请输入11位手机号');
setPhoneStatus("error"); setPhoneStatus('error');
return; return;
} }
setPhoneErrorMessage(""); setPhoneErrorMessage('');
setPhoneStatus(""); setPhoneStatus('');
props.choosedItem.nickName ? handleEditEmployee() : handleAddEmployee(); props.choosedItem.nickName ? handleEditEmployee() : handleAddEmployee();
} }
function handleChangeValues(name: string, value: any) { function handleChangeValues(name: string, value: any) {
switch (name) { switch (name) {
case "nickName": case 'nickName':
form.setFieldsValue({ nickName: value }); form.setFieldsValue({ nickName: value });
setName(value); setName(value);
return; return;
case "phone": case 'phone':
form.setFieldsValue({ phone: value }); form.setFieldsValue({ phone: value });
setPhone(value); setPhone(value);
setPhoneErrorMessage(""); setPhoneErrorMessage('');
setPhoneStatus(""); setPhoneStatus('');
return; return;
case "role": case 'role':
form.setFieldsValue({ role: value }); form.setFieldsValue({ role: value });
setRole(value); setRole(value);
return; return;
...@@ -177,9 +157,9 @@ function AddEmployeeModal(props: AddEmployeeModalProps) { ...@@ -177,9 +157,9 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
roleCodes: [role], roleCodes: [role],
avatar, avatar,
}; };
console.log("params", params); console.log('params', params);
StoreService.addEmployee(params).then((res) => { StoreService.addEmployee(params).then((res) => {
message.success("保存成功"); message.success('保存成功');
props.onClose(); props.onClose();
}); });
} }
...@@ -191,12 +171,12 @@ function AddEmployeeModal(props: AddEmployeeModalProps) { ...@@ -191,12 +171,12 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
roleCodes: [role], roleCodes: [role],
avatar, avatar,
storeUserId: storeUserId, storeUserId: storeUserId,
storeId:User.getStoreId() storeId: User.getStoreId(),
}; };
console.log("params", params); console.log('params', params);
StoreService.editEmployee(params).then((res) => { StoreService.editEmployee(params).then((res) => {
message.success("编辑成功"); message.success('编辑成功');
props.onClose(); props.onClose();
}); });
} }
...@@ -204,119 +184,94 @@ function AddEmployeeModal(props: AddEmployeeModalProps) { ...@@ -204,119 +184,94 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
return ( return (
<Modal <Modal
visible={true} visible={true}
title={`${props.choosedItem.nickName ? "编辑员工" : "添加员工"}`} title={`${props.choosedItem.nickName ? '编辑员工' : '添加员工'}`}
className="employee-add-modal" className='employee-add-modal'
width={680} width={680}
onCancel={props.onClose} onCancel={props.onClose}
onOk={handleOk} onOk={handleOk}
maskClosable={false} maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>} closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}>
>
<Row style={{ height: 271 }}> <Row style={{ height: 271 }}>
<Col span={18}> <Col span={18}>
<Form <Form {...layout} form={form} name='basic' initialValues={{ nickName: nickName, phone: phone, role: role }}>
{...layout}
form={form}
name="basic"
initialValues={{ nickName: nickName, phone: phone, role: role }}
>
<Form.Item <Form.Item
label="员工姓名" label='员工姓名'
name="nickName" name='nickName'
// rules={[{ required: true }]} // rules={[{ required: true }]}
// validateStatus={nameStatus} // validateStatus={nameStatus}
// help={nameErrorMsg || undefined} // help={nameErrorMsg || undefined}
style={{ marginBottom: 0 }} style={{ marginBottom: 0 }}>
> <div
<div style={{ style={{
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<WWOpenDataCom type="userName" openid={props.choosedItem.weChatAccount}/> <WWOpenDataCom type='userName' openid={props.choosedItem.weChatAccount} />
</div> </div>
</Form.Item> </Form.Item>
{props.isWorkWechat ? ( {props.isWorkWechat ? (
<Form.Item <Form.Item label='企业微信账号' name='weChatAccount'>
label="企业微信账号"
name="weChatAccount"
>
<Tooltip title={props.choosedItem.weChatAccount}> <Tooltip title={props.choosedItem.weChatAccount}>
<div style={{ <div
whiteSpace: 'nowrap', style={{
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
overflow: 'hidden', textOverflow: 'ellipsis',
}}>{props.choosedItem.weChatAccount}</div> overflow: 'hidden',
}}>
{props.choosedItem.weChatAccount}
</div>
</Tooltip> </Tooltip>
</Form.Item> </Form.Item>
) : ( ) : (
<Form.Item <Form.Item label='手机号码' name='phone' rules={[{ required: true }]} validateStatus={phoneStatus} help={phoneErrorMessage}>
label="手机号码"
name="phone"
rules={[{ required: true }]}
validateStatus={phoneStatus}
help={phoneErrorMessage}
>
<Input <Input
style={{ width: 200 }} style={{ width: 200 }}
placeholder="请输入手机号" placeholder='请输入手机号'
maxLength={11} maxLength={11}
autoComplete="off" autoComplete='off'
disabled={!!props.choosedItem.nickName} disabled={!!props.choosedItem.nickName}
onChange={(e) => { onChange={(e) => {
if (e.target.value.match(/^\d+$/)) { if (e.target.value.match(/^\d+$/)) {
handleChangeValues("phone", e.target.value); handleChangeValues('phone', e.target.value);
} else { } else {
setPhoneErrorMessage("只能输入数字"); setPhoneErrorMessage('只能输入数字');
setPhoneStatus("error"); setPhoneStatus('error');
} }
}} }}
/> />
</Form.Item> </Form.Item>
)} )}
<Form.Item <Form.Item label='所在部门' name='weChatAccount' style={{ marginBottom: 0 }}>
label="所在部门" <div
name="weChatAccount" style={{
style={{ marginBottom: 0 }} whiteSpace: 'nowrap',
> textOverflow: 'ellipsis',
<div style={{ overflow: 'hidden',
whiteSpace: 'nowrap', }}>
textOverflow: 'ellipsis', {props.choosedItem.depNameList.map((item: any, index: any) => {
overflow: 'hidden', return (
}}> <span>
{props.choosedItem.depNameList.map((item:any,index:any)=>{ <WWOpenDataCom type='departmentName' openid={item} />
return <span><WWOpenDataCom type="departmentName" openid={item}/>{index<props.choosedItem.depNameList.length-1?'、':''}</span> {index < props.choosedItem.depNameList.length - 1 ? '、' : ''}
})} </span>
</div> );
})}
</div>
</Form.Item> </Form.Item>
<Form.Item <Form.Item label='员工角色' name='role' rules={[{ required: false }]} style={{ marginBottom: '-2px !important' }} className='mt-26'>
label="员工角色"
name="role"
rules={[{ required: false }]}
style={{marginBottom:'-2px !important'}}
className="mt-26"
>
<RadioGroup <RadioGroup
onChange={(e) => { onChange={(e) => {
handleChangeValues("role", e.target.value); handleChangeValues('role', e.target.value);
}} }}
className="mt5" className='mt5'>
> <Radio value={'CloudLecturer'} className='mt-4'>
<Radio value={"CloudLecturer"} className="mt-4"> <span style={{ color: '#333' }}>普通讲师</span>
<span style={{ color: "#333" }}>普通讲师</span> <p className='radio-tip'>仅可查看/使用与自己相关的文件和课表,并进行上课</p>
<p className="radio-tip">
仅可查看/使用与自己相关的文件和课表,并进行上课
</p>
</Radio>
<Radio value={"CloudManager"}>
<span style={{ color: "#333" }}>管理员</span>
<p className="radio-tip">可执行学院中所有的操作</p>
</Radio> </Radio>
<Radio value={"CloudOperator"} className="mt-4"> <Radio value={'CloudManager'}>
<span style={{ color: "#333" }}>运营师</span> <span style={{ color: '#333' }}>管理员</span>
<p className="radio-tip"> <p className='radio-tip'>可执行学院中所有的操作</p>
仅可查看/转发培训计划内容,并查看其负责的学员学习进度
</p>
</Radio> </Radio>
</RadioGroup> </RadioGroup>
</Form.Item> </Form.Item>
...@@ -324,10 +279,10 @@ function AddEmployeeModal(props: AddEmployeeModalProps) { ...@@ -324,10 +279,10 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
</Col> </Col>
<Col span={6}> <Col span={6}>
<div className="avatar-box"> <div className='avatar-box'>
<div className="avatar-text">头像</div> <div className='avatar-text'>头像</div>
<div className="avatart-img"> <div className='avatart-img'>
<img className="avatar" src={avatar}></img> <img className='avatar' src={avatar}></img>
</div> </div>
</div> </div>
</Col> </Col>
......
import React, { useEffect, useState } from 'react';
import { Tabs } from 'antd';
import { Route, withRouter } from 'react-router-dom';
import Service from '@/common/js/service';
import Breadcrumbs from "@/components/Breadcrumbs";
import UserLearningData from './UserLearningData';
import DataInfo from './components/DataInfo'
import CourseTable from './components/CourseTable';
import DataAnalysic from './components/DataAnalysic';
import ExamTable from './components/ExamTable';
import StudyTable from './components/StudyTable';
import './index.less'
const { TabPane } = Tabs;
function DataCenter(props: any) {
const { match } = props;
const { params: { taskId } } = match;
const [info, setInfo] = useState<any>({})
const [tabKey, setTabKey] = useState<any>('')
const tabList = [
{
name: '课程目录',
key: 'course',
compoment: CourseTable,
show:()=>{
return true
}
},
{
name: '学习详情',
key: 'study',
compoment: StudyTable,
show:()=>{
return info.courseNum
}
},
{
name: '考试详情',
key: 'exam',
compoment: ExamTable,
show:()=>{
return info.examNum
}
},
]
useEffect(() => {
getInfo()
}, [])
function getInfo() {
Service.Hades('/public/hades/getTrainingTaskDetail', { taskId }).then((res: any) => {
res.result.trainingStageList.map((item: any) => {
item.open = true
})
res.result.cover = res.result.courseMediaVOS.filter((item: any) => item.contentType === 'COVER')[0] || {};
res.result.intro = res.result.courseMediaVOS.filter((item: any) => item.contentType === 'INTRO')[0] || {};
setInfo(res.result)
})
}
return <div className="page train-data-center">
<Breadcrumbs
navList="培训任务详情"
goBack={() => {
props.history.goBack();
}}
/>
<div className="content">
<DataInfo info={info} />
<Tabs defaultActiveKey={'course'} onChange={() => { }}>
{
tabList.map((item: any) => {
return item.show() && <TabPane tab={item.name} key={item.key}>
{
<item.compoment info={info} taskId={taskId} />
}
</TabPane>
})
}
</Tabs>
</div>
<Route
path={`${match.url}/analysic/:id`}
render={() => {
return <DataAnalysic />;
}}
/>
<Route path={`${props.match.url}/user-learning-data/:storeCustomerId`} render={() => <UserLearningData taskId={taskId} />} />
</div>
}
export default withRouter(DataCenter)
/*
* @Author: yuananting
* @Date: 2021-08-05 10:55:49
* @LastEditors: wufan
* @LastEditTime: 2021-08-13 17:10:58
* @Description: 个人学习详情
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState } from 'react';
import { Tabs } from 'antd';
import Breadcrumbs from '@/components/Breadcrumbs';
import WholeData from './components/WholeData';
import { withRouter } from 'react-router-dom';
import TestLinkTaskData from './components/TestLinkTaskData';
import CustomerLearnBasicInfo from './components/CustomerLearnBasicInfo';
import User from '@/common/js/user';
import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
import './UserLearningData.less';
const { TabPane } = Tabs;
function UserLearningData(props) {
const taskId = props.taskId;
const storeCustomerId = props.match.params.storeCustomerId.replace(/\?.+/, '');
const tabList = [
{
name: '全部',
key: 'whole',
compoment: WholeData,
},
{
name: '考试',
key: 'exam',
compoment: TestLinkTaskData, // todo
},
];
const [detail, setDetail] = useState({});
useEffect(() => {
getTaskCustomerDetail();
}, []);
function getTaskCustomerDetail() {
const params = {
storeCustomerId,
storeId: User.getStoreId(),
taskId,
};
TaskCenterService.getTaskCustomerDetail(params).then((res) => {
const { result = {} } = res;
setDetail(result);
});
}
return (
<div className='page user-learning-data'>
<Breadcrumbs
navList='学员学习数据'
goBack={() => {
props.history.goBack();
}}
/>
<CustomerLearnBasicInfo detail={detail} />
<div className='gap-line'></div>
<div className='content'>
<Tabs defaultActiveKey={'whole'} onChange={() => {}}>
{tabList.map((item) => {
return (
<TabPane tab={item.name} key={item.key}>
{<item.compoment detail={detail} taskId={taskId}/>}
</TabPane>
);
})}
</Tabs>
</div>
</div>
);
}
export default withRouter(UserLearningData);
.user-learning-data {
.header {
padding: 16px 28px;
.train-title {
font-size: 18px;
font-weight: 500;
color: #333333;
line-height: 25px;
position: relative;
&::before {
content: '';
position: absolute;
left: -8px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 10px;
background: #2966ff;
}
&::after {
content: '';
position: absolute;
left: -8px;
top: 75%;
transform: translateY(-50%);
width: 4px;
height: 2px;
background: #0acca4;
}
}
.data-info {
display: flex;
color: #666666;
margin-top: 16px;
margin-bottom: 8px;
.user-name {
margin-right: 32px;
}
.complete-progress {
display: inline-flex;
margin-right: 24px;
}
img {
width: 20px;
height: 20px;
margin-right: 8px;
}
}
}
.gap-line {
height: 10px;
background-color: #f0f2f5;
}
.content {
.ant-tabs-tab {
padding: 20px 20px 14px !important;
// margin-left: 18px;
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-16 17:35:15
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 22:06:40
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import RichText from '@/components/RichText';
import ExpiredCourseList from '../../train-task/components/ExpiredCourseList';
import ENUM from '../../enum';
import './course.less';
function CourseTable(props: any) {
const taskId = props.match.params.taskId.replace(/\?.+/, '');
const IndexText = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
const [list, setList] = useState<any[]>([]);
useEffect(() => {
setList(props.info.trainingStageList || []);
}, [props.info.trainingStageList]);
return (
<div className='courseTabContent'>
<div className='tips'>
{`培训目的:`}
<RichText url={props.info?.intro?.mediaUrl} />
</div>
<div className='coursecontent'>
{list?.map((item: any, index: number) => {
return (
<div className='task'>
<div
className='title'
onClick={() => {
const _list = [...list];
_list[index].open = !_list[index].open;
setList(_list);
}}>
{item.open ? <span className='icon iconfont open'>&#xe614;</span> : <span className='icon iconfont'>&#xe614;</span>}
{`${IndexText[index]}、 `}
{item.stageName}
</div>
{item.open && (
<div className='taskItemList'>
{item.contentVOList.map((_item: any, _index: number) => {
return (
<div className={_index == item.contentVOList.length - 1 ? 'item noBorder' : 'item'}>
<div className='name'>
<img className='type-option-icon' src={ENUM.LearningContentIcon[_item.courseType || _item.contentType]} />
<span style={{ marginRight: 4 }}> {`${index + 1}.${_index + 1}`}</span>
{_item.contentName}
{_item.courseType == 'LIVE' && <span className='desc'>{ENUM.courseStateShow[_item.courseState].title}</span>}
{_item.courseType == 'VOICE' && <span className='desc'>{`(共${_item.courseChapterNum}节)`}</span>}
</div>
</div>
);
})}
</div>
)}
</div>
);
})}
</div>
<div className='expired'>
<ExpiredCourseList taskId={taskId} />
</div>
</div>
);
}
export default withRouter(CourseTable);
/*
* @Author: yuananting
* @Date: 2021-08-12 18:08:59
* @LastEditors: yuananting
* @LastEditTime: 2021-08-12 18:17:19
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Progress, Space } from 'antd';
import ENUM from '../../enum';
function CustomerLearnBasicInfo(props) {
return (
<div className='header'>
<div className='train-title'>{props.detail.taskName}</div>
<div className='data-info'>
<div className='user-name'>学员:{props.detail.storeCustomerName}</div>
<div className='complete-progress'>
任务完成率:
<div style={{ width: 120 }}>
<Progress size={120} strokeColor='#2966FF' trailColor='#EAEAEA' percent={props.detail.learnFinishPercentage} size='small' />
</div>
</div>
<Space size={24}>
<div>
<img src={ENUM.LearningContentIcon['COURSE']} />
课程:{props.detail.courseFinishNum || 0}/{props.detail.courseAllNum || 0}
</div>
<div>
<img src={ENUM.LearningContentIcon['EXAM']} />
考试:{props.detail.examFinishNum || 0}/{props.detail.examAllNum || 0}
</div>
</Space>
</div>
</div>
);
}
export default CustomerLearnBasicInfo;
import React, { useState, useRef, useEffect, useContext } from 'react'
import { Route, withRouter } from 'react-router-dom';
import Breadcrumbs from "@/components/Breadcrumbs";
import UserData from './UserData';
import ExamData from './ExamData'
import Service from "@/common/js/service";
import { Tabs } from 'antd';
import User from "@/common/js/user";
import './dataAnalysic.less'
const { TabPane } = Tabs;
function DataAnalysic(props: any) {
const examDetailInit: any = {};
const [selectKey, setSelectKey] = useState('user')
const [examDetail, setExamDetail] = useState(examDetailInit);
const { match } = props;
const examId =match.params.id;
useEffect(() => {
queryExamDetail();
}, [])
function queryExamDetail() {
Service.Hades("public/hades/queryExamDetail", {
examId:examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
const { result } = res
setExamDetail(result)
})
}
return <div className="page dataAnalysic">
<Breadcrumbs navList={"考试数据"} goBack={props.history.goBack} />
<div className="box">
<div className="titleBox ">
考试名称:{examDetail.examName}
</div>
</div>
<div className="box" style={{ paddingTop: 0 }}>
<Tabs activeKey={selectKey} onChange={(key: any) => {
setSelectKey(key)
}}>
<TabPane tab="考试人员数据" key="user">
<UserData examDetail ={examDetail } examId={examId} />
</TabPane>
<TabPane tab="题目数据" key="exam">
<ExamData examDetail ={examDetail } examId={examId}></ExamData>
</TabPane>
</Tabs>
</div>
</div>
}
export default withRouter(DataAnalysic);
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { withRouter } from "react-router-dom";
import { Progress } from 'antd';
import ENUM from '../../enum'
import moment from 'moment'
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
function DataInfo(props: any) {
return <div className="infoBox">
<div className="left">
<div className="banner">
<img src={ props.info.cover?.mediaUrl|| defaultCover} alt="" />
</div>
<div className="textBox">
<div className="nameBox">
<div className="name">{props.info.taskName}</div>
<div className="status" style={{ color: ENUM.trainStatus[props.info.taskState || 'UN_START'].color, background: ENUM.trainStatus[props.info.taskState || 'UN_START'].background }}>{ENUM.trainStatus[props.info.taskState || 'UN_START'].text}</div>
</div>
<div className="info">
<span> {`创建人: ${props.info.createName} `}</span>
<span style={{ marginLeft: 24 }}> {`培训时间:${moment(props.info.startTime).format('YYYY-MM-DD HH:mm')} 至 ${moment(props.info.endTime).format('YYYY-MM-DD HH:mm')}`}</span>
</div>
<div className="info">
<div className="item">{`任务数: ${props.info.contentNum}`}</div>
{
!!props.info.courseNum && <div className="item"><span className="icon"><img src="https://image.xiaomaiketang.com/xm/6C2GjSpnDp.png" alt="" /></span>{`课程:${props.info.courseNum}`}</div>
}
{/* <div className="item"><span className="icon"><img src="https://image.xiaomaiketang.com/xm/6C2GjSpnDp.png" alt="" /></span>{`作业:4`}</div> */}
{
!!props.info.examNum && <div className="item"><span className="icon"><img src="https://image.xiaomaiketang.com/xm/fCDPp2Eenc.png" alt="" /></span>{`考试:${props.info.examNum}`}</div>
}
</div>
</div>
</div>
<div className="right">
<div className="prog">
<Progress type="circle" width={85} percent={parseInt(props.info.finishCustomerNum /(props.info.cultureCustomerNum)*100 as any)} strokeWidth={10}
format={percent => <div className='wcl'>
{`${(parseInt((props.info.finishCustomerNum || 0) /(props.info.cultureCustomerNum || 0)*100 as any))}% `}
<div>完成率</div>
</div>} />
{/* <div className='wcl'> */}
{/* {`${(parseInt(props.info.finishCustomerNum /(props.info.cultureCustomerNum)*100 as any))}% `} */}
{/* <div>完成率</div> */}
{/* </div> */}
</div>
<div className="num">
<div className="item"> {`指派人数:${props.info.cultureCustomerNum}`}</div>
<div className="item">{`学习人数:${props.info.learningCustomerNum}`}</div>
<div className="item">{`完成人数:${props.info.finishCustomerNum}`}</div>
</div>
</div>
</div>
}
export default withRouter(DataInfo)
\ No newline at end of file
import React, { useState, useRef, useEffect } from "react";
import Service from "@/common/js/service";
import { PageControl } from "@/components";
import { Input, Select, Tooltip, Button } from "antd";
import User from "@/common/js/user";
import { XMTable } from "@/components";
import college from "@/common/lottie/college.json";
import "./userData.less";
interface sortType {
type: "ascend" | "descend" | null | undefined;
}
function ExamData(props: any) {
const sortStatus: sortType = {
type: undefined,
};
const examDataInit: any = {};
const queryInit: any = { current: 1, size: 10, order: "SORT_ASC" };
const [examData, setUserData] = useState(examDataInit);
const [list, setList] = useState([]);
const [query, setQuery] = useState(queryInit);
const [total, setTotal] = useState(0);
const [field, setfield] = useState("");
const [allData, setAllData] = useState(0);
const [order, setOrder] = useState(sortStatus.type);
const questionTypeList = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
const userTypeEnum = {
WORK_WE_CHAT: "企业微信",
WE_CHAT: "微信",
};
const userExamStateEnum = {
EXAM: "进行中",
LACK_EXAM: "缺考",
FINISH_EXAM: "已考试",
};
const orderEnum = {
currentAccuracy: {
ascend: "ACCURACY_ASC",
descend: "ACCURACY_DESC",
},
};
const queryRef = useRef({});
useEffect(() => {
queryExamUserData();
}, []);
useEffect(() => {
queryRef.current = query;
queryExamUserDataList();
}, [query]);
function queryExamUserData() {
Service.Hades("public/hades/queryExamQuestionData", {
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setUserData(res.result);
});
}
function queryExamUserDataList() {
Service.Hades("public/hades/queryExamQuestionDataList", {
...query,
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setList(res.result.records);
setTotal(parseInt(res.result.total));
if (!allData) {
setAllData(parseInt(res.result.total));
}
});
}
const columns = [
{
title: "序号",
dataIndex: "sort",
width: 60,
render: (text: any, record: any, index: any) => <span>{index + 1}</span>,
},
{
title: "题目",
dataIndex: "questionStem",
ellipsis: true,
width: 350,
render: (val: any) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="aid-tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
</Tooltip>
);
},
},
{
title: "题型",
dataIndex: "questionType",
render: (text: any) => <span>{(questionTypeList as any)[text]}</span>,
filters: Object.keys(questionTypeList).map((key) => {
return {
text: (questionTypeList as any)[key],
value: key,
};
}),
},
{
title: "本次正确率",
dataIndex: "currentAccuracy",
sorter: true,
sortOrder: field === "currentAccuracy" ? order : sortStatus.type,
render: (text: any) => <span>{parseInt((text * 100) as any)}%</span>,
},
{
title: (
<div>
历史正确率{" "}
<Tooltip
overlayClassName="tool-list"
title="包含本次考试正确率"
placement="top"
overlayStyle={{ maxWidth: 700 }}
>
{" "}
<span
style={{ color: "rgba(191, 191, 191, 1)",fontWeight: 400 }}
className="icon iconfont"
>
&#xe61d;
</span>
</Tooltip>
</div>
),
dataIndex: "totalAccuracy",
render: (text: any) => <span>{parseInt((text * 100) as any)}%</span>,
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
console.log(filters, sorter);
setfield(sorter.field);
setOrder(sorter.order);
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field]);
let _query: any = { ...queryRef.current };
console.log(filters.questionType);
if (filters.questionType) {
console.log(233232);
_query.questionType = filters.questionType;
_query.current = 1;
} else {
delete _query.questionType;
}
_query.order = (orderEnum as any)[sorter.field][sorter.order];
setQuery(_query);
}
function download() {
Service.Hades("public/hades/exportExamData", {
// ...query,
examId: props.examId,
exportDataType: "EXAM_QUESTION_DATA",
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
const dom = (document as any).getElementById("load-play-back-excel");
dom.setAttribute("href", res.result);
dom.click();
});
}
return (
<div className="rr">
<a
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
></a>
<div className="dataPanal">
{!!examData.singleChoiceCnt && (
<div className="item">
<div className="num">
{Math.round((examData.singleChoiceAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fa;</span>单选题{" "}
<span>(共{examData.singleChoiceCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.multiChoiceCnt && (
<div className="item">
<div className="num">
{Math.round((examData.multiChoiceAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fb;</span>多选题
<span>(共{examData.multiChoiceCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.judgeCnt && (
<div className="item">
<div className="num">
{Math.round((examData.judgeAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fc;</span>判断题
<span>(共{examData.judgeCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.gapFillingCnt && (
<div className="item">
<div className="num">
{Math.round((examData.gapFillingAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fd;</span>填空题
<span>(共{examData.gapFillingCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.indefiniteChoiceCnt && (
<div className="item">
<div className="num">
{Math.round((examData.indefiniteChoiceAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fe;</span>不定项选择题{" "}
<span>(共{examData.indefiniteChoiceCnt}题)</span>
</div>
</div>
</div>
)}
</div>
{!!allData && (
<Button style={{ marginBottom: 12, marginTop: 12 }} onClick={download}>
导出
</Button>
)}
<div className="content">
<XMTable
renderEmpty={{
image: college,
description: '暂无数据'
}}
bordered
size="small"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
></XMTable>
{total > 0 && (
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
console.log(page);
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query);
}}
/>
)}
</div>
</div>
);
}
export default ExamData;
import React, { useEffect, useState } from 'react';
import { Route, withRouter } from 'react-router-dom';
import { PageControl, XMTable } from '@/components';
import Service from '@/common/js/service';
import User from '@/common/js/user';
function ExamTable(props: any) {
const { match } = props;
console.log(match)
const [query, setQuery] = useState<any>({
current: 1, size: 10,
taskId: props.taskId,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
});
const [list, setList] = useState<any[]>([]);
const [total, setTotal] = useState<any>(0);
useEffect(() => {
getList()
}, [query])
function getList() {
Service.Hades('public/hades/queryTrainingExamUserData', query).then((res: any) => {
setList(res.result.records);
setTotal(res.result.total);
})
}
const columns = [
{
title: '考试名称',
key: 'examName',
dataIndex: 'examName',
},
{
title: '完成人数',
key: 'finishCnt',
dataIndex: 'finishCnt',
},
{
title: '平均分',
key: 'averageScore',
dataIndex: 'averageScore',
render: (val: any, record: any) => {
return <div>{val || 0}</div>;
},
},
{
title: '及格人数',
key: 'passCnt',
dataIndex: 'passCnt',
render: (val: any, record: any) => {
return <div>{val || 0}</div>;
},
},
{
title: '及格率',
key: 'passPercentage',
dataIndex: 'passPercentage',
render: (val: any, record: any) => {
return <div>{val}%</div>;
},
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
render: (val: any, record: any) => {
return (
<div className='operate-area'>
<span className='operate-item' style={{ color: 'rgba(41, 102, 255, 1)', cursor: 'pointer' }} onClick={() => {
props.history.push({
pathname: `${match.url}/analysic/${record.examId}`,
});
}}>
查看数据
</span>
</div>
);
},
},
];
return <div className="study_Table">
<div style={{ marginTop: 12 }}>
<XMTable
renderEmpty={{
type: 'college',
description: '暂无数据',
}}
rowKey={(record: any) => record.storeCustomerId}
dataSource={list}
columns={columns}
pagination={false}
className='user-learning-table'
onChange={() => { }}
showSorterTooltip={false}
bordered
/>
{list.length > 0 && (
<div className='box-footer'>
<PageControl
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
setQuery({ ...query, current: page + 1 })
}}
/>
</div>
)}
</div>
</div>
}
export default withRouter(ExamTable)
\ No newline at end of file
import React, { useState, useEffect } from "react";
import "./LeftStageList.less";
import ENUM from "../../enum";
function LeftStageList(props) {
const { stageExamList, activeExam, activeStage } = props;
const [stageList, setStageList] = useState(stageExamList || []);
// 展开和收起阶段下面的考试
function handleToggleExpand(stageIndex) {
let _taskList = [...stageList];
_taskList = _taskList.map((item, index) => {
if (stageIndex === index) {
item.isShowMoreCourse = !item.isShowMoreCourse;
}
return item;
});
setStageList(_taskList || []);
}
return (
<div className="left-stage-list">
{stageList.map((item, index) => {
return (
<div key={index} className="stage-list">
<div
className={`stage-item ${
item.stageId === activeStage.stageId ? "active" : ""
}`}
onClick={() => handleToggleExpand(index)}
>
<div className="icon">
{item.isShowMoreCourse ? (
<span className="icon iconfont edit-icon">&#xe677;</span>
) : (
<span className="icon iconfont edit-icon">&#xe600;</span>
)}
</div>
<div className="stage-name oneLineText">
{ENUM.IndexToSort[index + 1]}{item.stageName}
</div>
</div>
{!item.isShowMoreCourse && (
<div className="exam-list">
{item.contentVOList && item.contentVOList.map((examItem, examIdndex) => {
return (
<div
key={examIdndex}
className={`exam-item ${
examItem.contentId === activeExam.contentId ? "active" : ""
}`}
onClick={()=>{
props.changeActiveExam(examItem,item)
}}
>
<div className="exam-name">
{item.sequence}.{examItem.sequence}{' '}{examItem.contentName}
</div>
</div>
);
})}
</div>
)}
</div>
);
})}
</div>
);
}
export default LeftStageList;
.left-stage-list {
width: 229px;
.stage-list {
.stage-item {
display: flex;
width: 229px;
height: 44px;
background: rgba(255, 255, 255, 0.06);
color: #666;
font-size: 14px;
color: #666;
line-height: 44px;
margin-right: 8px;
cursor: pointer;
.iconfont {
line-height: 15px;
font-size: 10px;
margin: 0 8px;
display: inline-block;
color: #666;
}
&.active {
background: rgba(41, 102, 255, 0.06);
color: #2966ff;
}
}
.exam-list {
.exam-item {
width: 229px;
height: 44px;
background: rgba(255, 255, 255, 0.06);
width: 100%;
font-size: 14px;
color: #666666;
line-height: 44px;
padding-left: 48px;
cursor: pointer;
.exam-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
&.active {
background-color: RGBA(243, 246, 250, 1);
}
}
}
}
}
import React, { useEffect, useRef, useState } from 'react';
import { Route, withRouter } from "react-router-dom";
import { Row, Input, Select, Tooltip, Col } from 'antd';
import RangePicker from '@/modules/common/DateRangePicker';
import WWOpenDataCom from '@/components/WWOpenDataCom';
import { PageControl, XMTable } from '@/components';
import Service from '@/common/js/service';
import ENUM from '../../enum';
import User from '@/common/js/user';
import moment from 'moment';
const { Search } = Input;
const { Option } = Select;
declare var formatDate: any;
function StudyTable(props: any) {
console.log(props, 'rtyjukl')
const [query, setQuery] = useState<any>({
size: 10,
taskId: props.taskId,
source: 0,
storeId: User.getStoreId(),
storeUserId: User.getStoreUserId(),
userId: User.getStoreUserId(),
});
const [list, setList] = useState<any[]>([]);
const [total, setTotal] = useState<any>(0);
const [name, setName] = useState<any>('');
const [current, setCurrent] = useState<any>(1);
const [orderFiled, setOrderFiled] = useState<any>(null);
const [sort, setSort] = useState<any>(null);
const [sortedList, setSortedList] = useState<any[]>([]);
const timer = useRef<any>(null);
const sortNameMap: any = {
learnPercentage: 'LEARN_NUM',
latelyLearnTime: 'LATE_LEARN_TIME',
startLearnTime: 'START_LEARN_TIME',
}
useEffect(() => {
getList()
}, [query])
useEffect(() => {
console.log(sort, orderFiled)
if (sort && orderFiled) {
const _list = [...list];
_list.sort((pre: any, next: any) => {
if (sort === 'ascend') {
return (pre[orderFiled] || 0) - (next[orderFiled] || 0)
} else {
return (next[orderFiled] || 0) - (pre[orderFiled] || 0)
}
})
setSortedList(_list)
} else {
setSortedList(list)
}
}, [list, sort, orderFiled])
function getList() {
Service.Hades('public/hades/getTaskCustomerRecordPage', query).then((res: any) => {
res.result.map((item: any) => {
item.department = item.departmentNameList?.map((_item: any) => {
if (_item == '1000') {
return '微信'
} else {
return <WWOpenDataCom type="departmentName" openid={_item} />
}
})
})
setList(res.result)
setTotal(res.result.length)
})
}
const columns = [
{
title: '学员',
key: 'storeCustomerName',
dataIndex: 'storeCustomerName',
render: (val: any, record: any) => {
return <div>{val}
<span
className="tag"
style={{ color: record.sourceEnum === 'WORK_WE_CHAT' ? '#2966FF' : '#1DCC65' }}
>{record.sourceEnum === 'WORK_WE_CHAT' ? '@企业微信' : '@微信'}</span>
</div>;
},
},
{
title: '部门',
key: 'departmentNameList',
dataIndex: 'departmentNameList',
render: (val: any, record: any) => {
return <div>
{
record.department?.map((item: any, index: number) => {
if ((index + 1) == record.department.length) {
return item
} else {
return [item, ';']
}
})
}</div>
},
},
{
title: '岗位',
key: 'postNameList',
dataIndex: 'postNameList',
},
{
title: '学习状态',
key: 'learnState',
dataIndex: 'learnState',
render: (val: any, record: any) => {
return <div>{ENUM.learnState[val].text}</div>;
},
},
{
title: <div>学习进度 <Tooltip
title={'已完成任务数/任务总数'}>
<span
style={{ color: "rgba(191, 191, 191, 1)", fontWeight: 400 }}
className="iconfont"
>&#xe61d;</span>
</Tooltip></div>,
key: 'learnPercentage',
sorter: true,
dataIndex: 'learnPercentage',
sortOrder: (orderFiled == 'learnPercentage') ? sort : null,
render: (val: any, record: any) => {
return `${val}%`;
},
},
{
title: '最近学习时间',
key: 'latelyLearnTime',
dataIndex: 'latelyLearnTime',
sorter: true,
width: 240,
sortOrder: (orderFiled == 'latelyLearnTime') ? sort : null,
render: (val: any, record: any) => {
return `${formatDate('YYYY-MM-DD H:i', parseInt(record.latelyLearnTime))}`;
},
},
{
title: '开始学习时间',
key: 'startLearnTime',
dataIndex: 'startLearnTime',
width: 240,
sorter: true,
field: 'yuiooo',
sortOrder: (orderFiled == 'startLearnTime') ? sort : null,
render: (val: any, record: any) => {
return <div>{formatDate('YYYY-MM-DD H:i', val)}</div>;
},
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
render: (val: any, record: any) => {
return (
<div className='operate-area'>
<span
className='operate-item'
style={{ color: 'rgba(41, 102, 255, 1)', cursor: 'pointer' }}
onClick={() => {
props.history.push(`${props.match.url}/user-learning-data/${record.storeCustomerId}`);
}}>
查看数据
</span>
</div>
);
},
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
console.log(sorter)
setOrderFiled(sorter.field || null)
setSort(sorter.order)
setCurrent(1)
}
return <div className="study_Table">
<div className="filter">
<Row>
<Col span={7}>
<div className='xm_search_item'>
<span className='label'>学员:</span>
<div className="search">
<Search
value={name}
placeholder='搜索学员名称'
onChange={(e) => {
setName(e.target.value);
clearTimeout(timer.current)
var name = e.target.value
timer.current = setTimeout(() => {
setQuery({
...query,
customerName: name
})
setCurrent(1)
}, 500)
}}
onSearch={() => {
}}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
</div>
</div>
</Col>
<Col span={7}>
<div className='xm_search_item'>
<span className='label '>学习状态:</span>
<div className="search">
<Select
style={{ width: '100%' }}
placeholder='请选择当前状态'
allowClear={true}
value={query.learnState}
onChange={(value) => {
setQuery({
...query,
learnState: value
})
setCurrent(1)
}}
suffixIcon={
<span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
&#xe835;
</span>
}>
{
Object.keys(ENUM.learnState).map((key: any) => {
return <Option value={key}>{ENUM.learnState[key].text}</Option>
})
}
</Select>
</div>
</div>
</Col>
<Col span={7}>
<div className='xm_search_item'>
<span className='label'>最近学习日期:</span>
<div className="search">
<RangePicker
style={{ width: '100%' }}
id='course_date_picker'
allowClear={false}
value={query.startTime ? [moment(query.startTime), moment(query.endTime)] : null}
format={'YYYY-MM-DD'}
onChange={(dates: any) => {
setQuery({
...query,
startTime: dates[0]?.startOf('day').valueOf(),
endTime: dates[0]?.endOf('day').valueOf(),
})
setCurrent(1)
}}
/>
</div>
</div>
</Col>
<Col offset={2} span={1} >
<Tooltip title='清空筛选'>
<span className='resetBtn iconfont icon' onClick={() => {
setName('');
setQuery({
size: 10,
taskId: props.taskId,
source: 0,
storeId: User.getStoreId(),
storeUserId: User.getStoreUserId(),
userId: User.getStoreUserId(),
})
setCurrent(1)
setSort('')
}}>
&#xe61b;{' '}
</span>
</Tooltip>
</Col>
</Row>
</div>
<div style={{ marginTop: 12 }}>
<XMTable
renderEmpty={{
type: 'college',
description: '暂无数据',
}}
rowKey={(record: any, index: any) => record.storeCustomerId + '_' + index}
dataSource={sortedList.slice((current - 1) * query.size, current * query.size)}
columns={columns}
pagination={false}
className='user-learning-table'
onChange={onChange}
showSorterTooltip={false}
bordered
/>
{list && list.length > 0 && (
<div className='box-footer'>
<PageControl
current={current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
setCurrent(page + 1)
}}
/>
</div>
)}
</div>
</div>
}
export default withRouter(StudyTable)
\ No newline at end of file
import React, { useState, useEffect } from "react";
import "./TestLinkTaskData.less";
import LeftStageList from "./LeftStageList";
import TestDetailPage from "@/modules/teach-tool/examination-manager/TestDetailPage";
import { Route, withRouter } from "react-router-dom";
import Service from "@/common/js/service";
import LottieIcon from "@/components/LottieIcon";
import User from '@/common/js/user';
function TestLinkTaskData(props) {
const [stageExamList, setStageExamList] = useState([]);
const [activeExam, setActiveExam] = useState({});
const [activeStage, setActiveStage] = useState({});
const [isShow, setIsShow] = useState(true);
const {taskId, detail: {storeCustomerId}} = props;
useEffect(() => {
getTrainingTaskDetail();
}, []);
function getTrainingTaskDetail() {
Service.Hades("public/hades/getTaskCustomerDetail", {
taskId: taskId,
storeId: User.getStoreId(),
storeCustomerId: storeCustomerId,
}).then((res) => {
const data = { ...res.result };
const { stageExamCustomerVOList = [] } = data;
if (!!stageExamCustomerVOList.length) {
setStageExamList(stageExamCustomerVOList);
setActiveStage(stageExamCustomerVOList[0]);
setActiveExam(stageExamCustomerVOList[0].contentVOList[0]);
const { contentId, paperId } = stageExamCustomerVOList[0].contentVOList[0];
const { match } = props;
const path = `${match.url}/test-detail/${contentId}?paperId=${paperId}1&userId=${storeCustomerId}&fromTrainingTask=true`;
window.RCHistory.replace(path);
}
});
}
function changeActiveExam(_activeExam, _activeStage) {
console.log("_activeExam", _activeExam);
setActiveExam(_activeExam);
setActiveStage(_activeStage);
const { contentId, paperId } = _activeExam;
const { match } = props;
const path = `${
match.url
}/test-detail/${contentId}?paperId=${paperId}&userId=${111}`;
window.RCHistory.replace(path);
}
function toggleShowBar(diretion) {
if (diretion === "left") {
setIsShow(false);
} else {
setIsShow(true);
}
}
return (
<div>
{stageExamList.length ? (
<div className={`test-link-task-data ${!isShow ? 'hidden':''}`}>
<div className={`left-stage-list-box ${!isShow ? 'hidden':''}`}>
{isShow ? (
<div
className="left-bar"
onClick={() => {
toggleShowBar("left");
}}
></div>
) : (
<div
className="right-bar"
onClick={() => {
toggleShowBar("right");
}}
></div>
)}
<LeftStageList
stageExamList={stageExamList}
activeExam={activeExam}
changeActiveExam={changeActiveExam}
activeStage={activeStage}
/>
</div>
<div className={`right-exam-data-box ${!isShow ? 'hidden':''}`}>
{/* 未考过的考试显示暂无考试数据,否则请求接口会报错 */}
{
activeExam.learnFinishPercentage === 0 ?
<LottieIcon
title={<span className="desc">暂无考试数据</span>}
type="college"
size={150}
/> :
<Route
path={`${props.match.url}/test-detail/:testId`}
component={TestDetailPage}
/>
}
</div>
</div>
) : (
<LottieIcon
title={<span className="desc">暂无考试数据</span>}
type="college"
size={150}
/>
)}
</div>
);
}
export default withRouter(TestLinkTaskData);
.test-link-task-data {
position: relative;
height: calc(100vh - 300px);
border-right: 1px solid #cccccc;
display: flex;
.left-stage-list-box {
border-right: 1px solid #cccccc;
width: 245px;
height: 100%;
overflow-y: auto;
position: relative;
overflow-x: hidden;
&.hidden {
border-left: 1px solid #cccccc;
border-right: none;
width: 15px;
.left-stage-list {
display: none;
}
}
.left-bar {
width: 8px;
height: 68px;
background: #d6d6d6;
border-radius: 12px 0px 0px 12px;
position: absolute;
right: 0;
top: 50%;
&::before {
content: "";
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
width: 0;
height: 0;
transform: rotate(45deg);
background-color: #fff;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.2);
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #fff;
}
&:hover {
width: 12px;
height: 68px;
background: #666666;
border-radius: 12px 0px 0px 12px;
}
}
.right-bar {
width: 8px;
height: 68px;
background: #d6d6d6;
border-radius: 0px 12px 12px 0px;
position: absolute;
left: 0;
top: 50%;
&::before {
content: "";
left: -190%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
width: 0;
height: 0;
transform: rotate(315deg);
background-color: #fff;
box-shadow: 0 0 6px 0 rgb(0 0 0 / 20%);
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #fff;
}
&:hover {
width: 12px;
height: 68px;
background: #666666;
border-radius: 0px 12px 12px 0px;
&::before {
content: "";
left: -120%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
width: 0;
height: 0;
transform: rotate(315deg);
background-color: #fff;
box-shadow: 0 0 6px 0 rgb(0 0 0 / 20%);
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #fff;
}
}
}
}
.right-exam-data-box {
width: calc(100vw - 450px);
height: 100%;
overflow-y: auto;
margin-top: -16px;
overflow-x: hidden;
@media screen and (max-width: 1400px) {
.lottie-icon {
text-align: center;
margin-top: 153px;
margin-bottom: 50px;
}
}
&.hidden {
width: calc(100vw - 200px);
}
}
}
import React, { useState, useRef, useEffect } from "react";
import Service from "@/common/js/service";
import { PageControl } from "@/components";
import { Input, Select, Tooltip, Table, Button } from "antd";
import { ColumnsType } from "antd/es/table";
import User from "@/common/js/user";
import moment from "moment";
import { XMTable } from "@/components";
import college from "@/common/lottie/college.json";
import "./userData.less";
const { Search } = Input;
const { Option } = Select;
declare var window: any;
interface sortType {
type: "ascend" | "descend" | null | undefined;
}
interface User {
key: number;
name: string;
}
function DataAnalysic(props: any) {
const sortStatus: sortType = {
type: undefined,
};
const useDataInit: any = {};
const queryInit: any = { current: 1, size: 10 };
const [useData, setUserData] = useState(useDataInit);
const [list, setList] = useState([]);
const [query, setQuery] = useState(queryInit);
const [total, setTotal] = useState(0);
const [field, setfield] = useState("");
const [allData, setAllData] = useState(0);
const [order, setOrder] = useState(sortStatus.type);
const userTypeEnum = {
WORK_WE_CHAT: "企业微信",
WE_CHAT: "微信",
};
const userExamStateEnum = {
EXAM: "进行中",
LACK_EXAM: "缺考",
FINISH_EXAM: "已考试",
};
const ExamPassColorEnum = {
EXAM_FAIL: "rgba(255, 79, 79, 1)",
EXAM_PASS: "rgba(59, 189, 170, 1)",
};
const ExamPassEnum = {
EXAM_FAIL: "不及格",
EXAM_PASS: "及格",
};
const userExamStateColorEnum = {
EXAM: "rgba(35, 143, 255, 1)",
LACK_EXAM: "rgba(204, 204, 204, 1)",
FINISH_EXAM: "rgba(47, 200, 60, 1)",
};
const orderEnum = {
score: {
ascend: "EXAM_SCORE_ASC",
descend: "EXAM_SCORE_DESC",
},
userDuration: {
ascend: "USER_DURATION_ASC",
descend: "USER_DURATION_DESC",
},
};
const queryRef = useRef({});
useEffect(() => {
queryExamUserData();
}, []);
useEffect(() => {
queryRef.current = query;
queryExamUserDataList();
}, [query]);
function queryExamUserData() {
Service.Hades("public/hades/queryExamUserData", {
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setUserData(res.result);
});
}
function queryExamUserDataList() {
Service.Hades("public/hades/queryExamUserDataList", {
...query,
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setList(res.result.records);
setTotal(parseInt(res.result.total));
if (!allData) {
setAllData(parseInt(res.result.total));
}
});
}
const columns: ColumnsType<User> = [
{
title: "学员",
dataIndex: "userName",
render: (text: any, record: any) => (
<span>
{text}
<span
style={{
color:
record.userSource === "WORK_WE_CHAT"
? "rgba(255, 157, 20, 1)"
: "rgba(29, 204, 101, 1)",
}}
>
@{(userTypeEnum as any)[record.userSource]}
</span>
</span>
),
},
{
title: "手机号",
dataIndex: "phone",
},
{
title: "考试状态",
dataIndex: "userExamState",
render: (text: any) => (
<span>
{" "}
<span
className="exstatus"
style={{ background: (userExamStateColorEnum as any)[text] }}
></span>{" "}
{(userExamStateEnum as any)[text]}
</span>
),
},
{
title: "考试成绩",
dataIndex: "score",
sorter: true,
sortOrder: field === "score" ? order : sortStatus.type,
render: (text: any, record: any) => (
<span>
{" "}
{text}{" "}
<span
style={{
border: `1px solid ${
(ExamPassColorEnum as any)[record.examPass]
}`,
fontSize: 12,
color: (ExamPassColorEnum as any)[record.examPass],
display: "inline-block",
padding: "0px 2px",
}}
>
{(ExamPassEnum as any)[record.examPass]}
</span>
</span>
),
},
{
title: "进入考试时间",
dataIndex: "examStartTime",
render: (text: any) => (
<span>{moment(text).format("YYYY-MM-DD HH:mm")}</span>
),
},
{
title: "考试用时",
dataIndex: "userDuration",
sorter: true,
sortOrder: field === "userDuration" ? order : sortStatus.type,
render: (text: any, record: any) => (
<span>
{record.userExamState === "FINISH_EXAM"
? window.formatHourTime(text)
: "-"}{" "}
</span>
),
},
//TODO:
{
title: "操作",
key: "",
dataIndex: "edit",
render: (value: any, record: any) => {
return (
<Choose>
<When condition={record.userExamState === "FINISH_EXAM"}>
<div
className="answer-detail"
onClick={() => {
checkAnswerDetail(record);
}}
>
答题详情
</div>
</When>
<Otherwise>-</Otherwise>
</Choose>
);
},
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
setfield(sorter.field);
setOrder(sorter.order);
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field]);
let _query: any = { ...queryRef.current };
_query.order = (orderEnum as any)[sorter.field][sorter.order];
setQuery(_query);
}
function download() {
Service.Hades("public/hades/exportExamData", {
// ...query,
examId: props.examId,
exportDataType: "EXAM_USER_DATA",
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
const dom = (document as any).getElementById("load-play-back-excel");
dom.setAttribute("href", res.result);
dom.click();
});
}
//查看答题详情
function checkAnswerDetail(record: any) {
const { paperId, userExamState } = props.examDetail?.examPaper;
const { userId } = record;
window.RCHistory.push({
pathname: `/test-detail/${props.examId}?paperId=${paperId}&userExamState=${userExamState}&userId=${userId}`,
});
}
return (
<div className="rr">
<a
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
>
111
</a>
<div className="dataPanal">
<div className="item">
<div className="num">{useData.joinCnt || 0}</div>
<div className="percent"></div>
<div className="subTitle">参与人数</div>
</div>
<div className="item">
<div className="num">{useData.finishCnt || 0}</div>
<div className="percent">
占比
{parseInt(
((useData.finishCnt || 0) / (useData.joinCnt || 1)) * 100 + ""
)}
%
</div>
<div className="subTitle">完成考试数 (人)</div>
</div>
<div className="item">
<div className="num">{useData.passCnt || 0}</div>
<div className="percent">
占比
{parseInt(
((useData.passCnt || 0) / (useData.finishCnt || 1)) * 100 + ""
)}
%
</div>
<div className="subTitle">及格数 (人)</div>
</div>
<div className="item">
<div className="num">{useData.averageScore || 0}</div>
<div className="percent">
总分{props.examDetail?.examPaper?.totalScore}
</div>
<div className="subTitle">平均分</div>
</div>
<div className="item">
<div className="num">
{" "}
{window.formatHourTime(useData.averageDuration || 0)}{" "}
</div>
<div className="percent"></div>
<div className="subTitle">平均用时</div>
</div>
</div>
<div className="xm-search-filter" style={{ marginTop: "24px" }}>
<div style={{ display: "flex" }}>
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">学员:</span>
<Search
value={query.examName}
className="search-input"
placeholder="搜索学员名或手机号"
onChange={(e) => {
const _query = { ...query };
_query.searchKey = e.target.value;
setQuery(_query);
}}
onSearch={() => {}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="search-name">学员类型:</span>
<Select
value={query.userSource}
placeholder="请选择学员类型"
onChange={(val) => {
const _query = { ...query };
_query.userSource = val;
setQuery(_query);
}}
className="search-input"
allowClear
>
{Object.keys(userTypeEnum).map((key: any) => {
return (
<Option value={key} key={key}>
{(userTypeEnum as any)[key]}
</Option>
);
})}
</Select>
</div>
<div className="search-condition__item">
<span className="search-name">考试状态:</span>
<Select
value={query.userExamState}
placeholder="请选择考试状态"
onChange={(val) => {
const _query = { ...query };
_query.userExamState = val;
setQuery(_query);
}}
className="search-input"
allowClear
>
{Object.keys(userExamStateEnum).map((key: any) => {
return (
<Option value={key} key={key}>
{(userExamStateEnum as any)[key]}
</Option>
);
})}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={() => {
setfield("");
setQuery({ current: 1, size: 10 });
}}
>
&#xe61b;{" "}
</span>
</Tooltip>
</div>
</div>
</div>
{!!allData && (
<Button style={{ marginBottom: 12 }} onClick={download}>
导出
</Button>
)}
<div className="content analysic-content">
<XMTable
renderEmpty={{
image: college,
description: '暂无数据'
}}
bordered
size="small"
rowClassName="analysic-content-row"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
></XMTable>
{total > 0 && (
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
console.log(page);
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query);
}}
/>
)}
</div>
</div>
);
}
export default DataAnalysic;
/*
* @Author: yuananting
* @Date: 2021-08-05 11:26:25
* @LastEditors: yuananting
* @LastEditTime: 2021-08-18 18:27:06
* @Description: 个人学习详情-全部tab页
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { Collapse, Progress } from 'antd';
import './WholeData.less';
import ENUM from '../../enum';
const { Panel } = Collapse;
function WholeData(props) {
const SortConvert = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
const courseStateShow = {
UN_START: {
title: '待开播',
},
STARTING: {
title: '直播中',
},
FINISH: {
title: '回放',
},
EXPIRED: {
title: '未成功开课',
},
};
const [stageCustomerList, setStageCustomerList] = useState([]);
useEffect(() => {
setStageCustomerList(props.detail.stageCustomerVOList);
}, [props]);
// 渲染阶段信息
function renderStageInfo(item, index) {
return (
<div className='stage-info__item'>
<span className='stage-name'>
{SortConvert[index]}{item.stageName}
</span>
<span className='extra-info'>
{item.learnFinishNum || 0}/{item.learnAllNum || item.contentVOList.length}
</span>
</div>
);
}
// 渲染学习内容信息
function renderContentInfo(contentItem, contentIndex, index) {
const { contentName, contentType, courseChapterNum, courseState, learnFinishPercentage, courseType } = contentItem;
return (
<div className='content-info__item'>
<div className='basic-info'>
<img src={ENUM.LearningContentIcon[courseType || contentType]} />
<span className='content-name'>
{contentIndex + 1}.{index + 1} {contentName}
</span>
{courseType === 'LIVE' && <span className='extra-info'>{courseStateShow[courseState].title}</span>}
{courseType === 'VOICE' && <span className='extra-info'>(共{courseChapterNum || 1}小节)</span>}
</div>
<div className='percent-info'>
{learnFinishPercentage === 100 ? (
<span className='icon iconfont'>&#xe621;</span>
) : (
<Progress
width={20}
strokeWidth={12}
type='circle'
trailColor='#EAEAEA'
strokeColor={{
'0%': '#38B7F3',
'100%': '#1A80E2',
}}
percent={learnFinishPercentage || 0}
/>
)}
<span className='text'>{learnFinishPercentage === 100 ? '已完成' : `${learnFinishPercentage || 0}%`}</span>
</div>
</div>
);
}
return (
<div className='whole-data-container'>
<Collapse ghost>
{stageCustomerList?.length > 0 &&
stageCustomerList.map((item, index) => {
return (
<Panel header={renderStageInfo(item, index)} key={index}>
{item.contentVOList.map((contentItem, contentIndex) => {
return renderContentInfo(contentItem, contentIndex, index);
})}
</Panel>
);
})}
</Collapse>
</div>
);
}
export default WholeData;
.whole-data-container {
.ant-collapse-header {
padding: 15px 16px !important;
background-color: #f7f8f9;
}
.stage-info {
&__item {
width: calc(100% - 24px);
display: inline-flex;
align-items: center;
line-height: 20px;
.stage-name {
color: #333333;
}
.extra-info {
margin-left: 8px;
color: #666666;
}
}
}
.content-info {
&__item {
padding: 14px 16px;
margin-left: 40px;
border-bottom: 1px dotted #e8e8e8;
vertical-align: middle;
display: flex;
justify-content: space-between;
* {
vertical-align: middle;
}
.basic-info {
.content-name {
margin-left: 12px;
color: #333333;
}
.extra-info {
margin-left: 8px;
color: #999999;
}
img {
width: 20px;
height: 20px;
}
}
.percent-info {
width: 70px;
.ant-progress-text {
display: none;
}
.icon {
color: #2966ff;
font-size: 20px;
}
.text {
margin-left: 6px;
color: #666666;
}
}
}
}
}
.courseTabContent{
padding-bottom: 24px;
.tips{
// height: 48px;
background: #F7F8F9;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
padding: 14px 16px;
}
.coursecontent{
margin-top: 16px;
margin-bottom: 16px;
.title{
padding: 14px 16px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
background: #F7F8F9;
border-radius: 2px;
cursor: pointer;
.icon{
color: rgba(153, 153, 153, 1);
transform: scale(0.8);
display: inline-block;
transform-origin: 50% 50%;
margin-right: 4px;
&.open{
transform:rotate(180deg) scale(0.8);
}
}
}
.taskItemList{
margin-left: 64px;
.item{
padding: 16px 0px 16px 16px;
border-bottom: 1px dashed #E8E8E8;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
.name{
img{
width: 20px;
position: relative;
top: -2px;
}
}
&.noBorder{
border:none;
}
.desc{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 20px;
margin-left: 8px;
}
}
}
}
.expired{
.title{
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 22px;
}
.desc{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 20px;
margin: 12px 0;
}
.list{
.item{
display: flex;
justify-content: space-between;
background: #F7F8F9;
border-radius: 2px;
padding: 16px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 20px;
span{
margin-right: 12px;
}
.na{
color: #333333;
}
.ope{
color: rgba(41, 102, 255, 1);
cursor: pointer;
}
}
}
}
}
.dataAnalysic {
.titleBox {
position: relative;
font-size: 19px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
background: #ffffff;
&::before {
width: 4px;
height: 12px;
content: '';
background-image: linear-gradient(#2966ff 83.5%, #0acca4 16.5%);
display: inline-block;
margin-right: 8px;
}
}
.ant-tabs-content-holder {
margin-top: 8px;
}
}
.study_Table{
}
\ No newline at end of file
.dataPanal {
border-radius: 4px;
border: 1px solid #e8e8e8;
display: flex;
.item {
text-align: center;
// width: 29.9%;
position: relative;
flex: 1;
.num {
font-size: 26px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
margin-top: 12px;
}
.percent {
margin-top: 6px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 17px;
height: 20px;
margin-bottom: 18px;
}
.subTitle {
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 12px;
}
.type {
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
span {
color: rgba(153, 153, 153, 1);
}
.icon {
color: rgba(204, 204, 204, 1);
font-size: 16px;
margin-right: 4px;
position: relative;
top: 1px;
}
}
&:after {
content: '';
width: 0px;
height: 40px;
position: absolute;
width: 1px;
background-color: rgba(232, 232, 232, 1);
top: 40px;
right: 0px;
}
&:last-child {
&:after {
display: none;
}
}
}
.exstatus {
width: 4px;
height: 4px;
background: rgb(35, 143, 255);
display: inline-block;
border-radius: 50%;
position: relative;
top: -4px;
}
}
.answer-detail {
color: rgb(35, 143, 255);
}
.analysic-content {
.ant-table-bordered .ant-table-tbody tr {
&.analysic-content-row {
height: 50px;
}
}
}
.train-data-center{
.content{
margin: 0 16px;
}
.infoBox{
background: #FFFFFF;
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.05);
border-radius: 2px;
display: flex;
height: 141px;
justify-content: space-between;
margin: 0 0px 16px;
.left{
display: flex;
.banner{
padding: 16px;
img{
width: 194px;
height: 109px;
}
}
.textBox{
padding-top: 1px;
.nameBox{
margin-top: 12px;
margin-bottom: 34px;
display: flex;
.name{
font-size: 18px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 25px;
}
.status{
padding: 0px 8px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #999999;
line-height: 20px;
text-align: center;
margin-left: 24px;
position: relative;
top: 2px;
height: 20px;
}
}
.info{
margin-bottom: 12px;
.icon{
position: relative;
top: -2px;
margin-right: 8px;
img{
width: 20px;
}
}
span,.item{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
display: inline-block;
}
.item{
margin-right: 24px;
}
}
}
}
.right{
display: flex;
margin-right: 32px;
.prog{
width: 85px;
margin-top: 27px;
margin-right: 24px;
position: relative;
.wcl{
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #666666;
line-height: 22px;
text-align: center;
// left: 0px;
// top: 20px;
// width: 100%;
// height: 100%;
// display: none;
// position: absolute;
// z-index: 1;
}
// &:hover{
// .wcl{
// display: block;
// }
// }
}
.num{
margin-top: 27px;
.item{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 12px;
}
}
}
}
}
\ No newline at end of file
/*
* @Author: yuananting
* @Date: 2021-08-03 18:10:15
* @LastEditors: wufan
* @LastEditTime: 2021-08-09 18:24:21
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
const ENUM: any = {
trainStatus: {
UN_START: {
text: "未开始",
color: "rgba(255, 178, 0, 1)",
background: "rgba(255, 178, 0, 0.1)",
},
STARTING: {
text: "进行中",
color: "rgba(41, 102, 255, 1)",
background: "rgba(41, 102, 255, 0.1)",
},
FINISH: {
text: "已结束",
color: "rgba(153, 153, 153, 1)",
background: "rgba(153, 153, 153, 0.1)",
},
},
learnState: {
UN_FINISH: {
text: "未完成",
},
FINISH: {
text: "已完成",
},
OVERDUE: {
text: "已逾期",
},
},
courseStateShow: {
UN_START: {
title: '待开播',
},
STARTING: {
title: '直播中',
},
FINISH: {
title: '回放',
},
EXPIRED: {
title: '未成功开课',
},
},
LearnState: {
UN_PLAY: {
text: '未开始',
},
UNDER_WAY: {
text: '进行中',
},
FINISH: {
text: '已完成',
},
},
LearningContentIcon: {
COURSE: 'https://image.xiaomaiketang.com/xm/6C2GjSpnDp.png',
LIVE: 'https://image.xiaomaiketang.com/xm/jyFhCtaKfi.png',
VOICE: 'https://image.xiaomaiketang.com/xm/2T2k5Tbmpy.png',
PICTURE: 'https://image.xiaomaiketang.com/xm/yzjNwGX6TY.png',
EXAM: 'https://image.xiaomaiketang.com/xm/fCDPp2Eenc.png',
HOMEWORK: 'https://image.xiaomaiketang.com/xm/hShsAzzppZ.png',
},
// index to 中文序号
IndexToSort: {
1: '一',
2: '二',
3: '三',
4: '四',
5: '五',
6: '六',
7: '七',
8: '八',
9: '九',
10: '十',
11: '十一'
}
}
export default ENUM
\ No newline at end of file
/*
* @Author: yuananting
* @Date: 2021-07-29 13:57:03
* @LastEditors: yuananting
* @LastEditTime: 2021-08-18 10:53:45
* @Description: 任务中心-培训任务-新建页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState, useRef } from 'react';
import { Button, message, Modal } from 'antd';
import ShowTips from '@/components/ShowTips';
import Breadcrumbs from '@/components/Breadcrumbs';
import BasicInfo from './components/BasicInfo';
import TrainContent from './components/TrainContent';
import User from '@/common/js/user';
import _ from 'underscore';
import './AddTrainTask.less';
import Upload from '@/core/upload';
import { randomString } from '@/domains/basic-domain/utils';
import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
import Bus from '@/core/bus';
import $ from 'jquery';
function AddTrainTask() {
const type = window.getParameterByName('type');
const taskId = window.getParameterByName('taskId');
const taskState = window.getParameterByName('taskState');
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const DEFAULT_BASIC_INFO = {
createId: User.getStoreUserId(),
taskName: '', // 培训任务名称
coverUrl: defaultCover,
coverId: null,
helpStoreUserIds: [
{
checkedId: User.getStoreUserId(),
checkedName: User.getStoreUserName(),
},
], // 指定协同者id
timeType: 'FOREVER', // 培训时间,默认永久有效
startTime: null, // 固定时间段-开始时间
endTime: null, // 固定时间段-结束时间
learnType: 'FREEDOM', // 学习模式,默认自由学习
assignList: [], // 指派列表-assignId assignType
introduce: null, // 培训目的
loadintroduce: false,
};
const DEFAULT_STAGE_LIST = [
{
stageName: '阶段一',
contentList: [],
check: false,
sequence: 1,
},
];
const DEFAULT_ORIGIN_STAGE_LIST = [
{
stageName: '阶段一',
contentList: [],
check: false,
sequence: 1,
},
];
const [activeStep, setActiveStep] = useState('BASIC_INFO');
const [basicInfo, setBasicInfo] = useState(DEFAULT_BASIC_INFO);
const [stageList, setStageList] = useState(DEFAULT_STAGE_LIST);
const [percentCompleteLive, setPercentCompleteLive] = useState(80); // 完成百分比
const [percentCompleteVideo, setPercentCompleteVideo] = useState(80); // 完成百分比
const [percentCompletePicture, setPercentCompletePicture] = useState(80); // 完成百分比
const [startCheck, setStartCheck] = useState(false); // 是否启动校验
const basicInfoRef = useRef(null);
const [originBasicInfo, setOriginBasicInfo] = useState(DEFAULT_BASIC_INFO);
const [originStageList, setOriginStageList] = useState(DEFAULT_ORIGIN_STAGE_LIST);
const [originPercentCompleteLive, setOriginPercentCompleteLive] = useState(80); // 完成百分比
const [originPercentCompleteVideo, setOriginPercentCompleteVideo] = useState(80); // 完成百分比
const [originPercentCompletePicture, setOriginPercentCompletePicture] = useState(80); // 完成百分比
useEffect(() => {
basicInfoRef.current = basicInfo;
}, [basicInfo]);
useEffect(() => {
if (type === 'edit') {
initTaskData();
// 编辑回显数据详情
function initTaskData() {
TaskCenterService.getTrainingTaskDetail({
taskId,
}).then((res) => {
const {
result: {
created,
createId,
taskName,
courseMediaVOS,
helpStoreUsers,
timeType,
startTime,
endTime,
learnType,
assignList,
percentCompleteLive,
percentCompletePicture,
percentCompleteVideo,
trainingStageList,
},
} = res;
const _assignList = assignList
? assignList.map((item) => {
item.checkedId = item.assignId;
item.checkedName = item.assignName;
item.checkedType = item.assignType;
return item;
})
: [];
const coverInfo = courseMediaVOS.filter((item) => item.contentType === 'COVER')[0];
const coverUrl = coverInfo ? coverInfo.mediaUrl : defaultCover;
const coverId = coverInfo ? coverInfo.mediaContent : null;
const _helpStoreUserIds = helpStoreUsers.map((item) => {
item.checkedName = item.storeUserName;
item.checkedId = item.storeUserId;
return item;
});
const ITEM_BASIC_INFO = {
createId,
assignList: _assignList,
taskName,
coverUrl,
coverId,
helpStoreUserIds: _helpStoreUserIds,
timeType,
startTime: timeType === 'FOREVER' ? created : startTime,
endTime,
learnType,
};
const _stageList = trainingStageList.map((item) => {
item.contentList = item.contentVOList;
item.type = 'text';
return item;
});
const _originStageList = _stageList.map((item) => Object.assign({}, item));
const introduceInfo = courseMediaVOS.filter((item) => item.contentType === 'INTRO')[0];
setBasicInfo({ ...basicInfoRef.current, ...ITEM_BASIC_INFO, loadintroduce: !introduceInfo });
setOriginBasicInfo({ ...basicInfoRef.current, ...ITEM_BASIC_INFO, loadintroduce: !introduceInfo });
setPercentCompleteLive(percentCompleteLive);
setPercentCompleteVideo(percentCompleteVideo);
setPercentCompletePicture(percentCompletePicture);
setOriginPercentCompleteLive(percentCompleteLive);
setOriginPercentCompleteVideo(percentCompleteVideo);
setOriginPercentCompletePicture(percentCompletePicture);
setStageList(_stageList);
setOriginStageList(_originStageList);
introduceInfo && getTextDetail('introduce', introduceInfo);
});
}
}
}, [taskId, type]);
// 获取培训目的内容
function getTextDetail(key, info) {
if (info.mediaType === 'RESOURCE_ID') {
$.ajax({
data: {},
type: 'GET',
url: info.mediaUrl,
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
success: (res) => {
setBasicInfo({
...basicInfoRef.current,
[key]: res,
[`load${key}`]: true,
});
setOriginBasicInfo({
...basicInfoRef.current,
[key]: res,
[`load${key}`]: true,
});
},
});
} else if (info.mediaType === 'TEXT') {
setBasicInfo({
...basicInfoRef.current,
[key]: info.mediaContent,
[`load${key}`]: true,
});
setOriginBasicInfo({
...basicInfoRef.current,
[key]: info.mediaContent,
[`load${key}`]: true,
});
}
}
// 渲染底部操作按钮
function renderFooter() {
return (
<Choose>
<When condition={activeStep === 'BASIC_INFO'}>
<div className='footer shrink-footer'>
<Button onClick={handleGoBack}>取消</Button>
<Button onClick={() => handleSubmit('NO')}>保存</Button>
<Button type='primary' onClick={() => setActiveStep('TRAIN_CONTENT')}>
下一步
</Button>
</div>
</When>
<Otherwise>
<div className='footer shrink-footer'>
<Button onClick={handleGoBack}>取消</Button>
<Button onClick={() => setActiveStep('BASIC_INFO')}>上一步</Button>
<Button onClick={() => handleSubmit('NO')}>保存</Button>
<Button type='primary' onClick={() => handleSubmit('YES')}>
保存并发布
</Button>
</div>
</Otherwise>
</Choose>
);
}
// 确认保存新建
function submitRemote(introduceId, issue) {
const { createId, assignList, endTime, helpStoreUserIds, learnType, startTime, taskName, timeType, coverId } = basicInfo;
let _scheduleMediaRequests = [];
if (coverId) {
_scheduleMediaRequests.push({
contentType: 'COVER',
mediaContent: coverId,
mediaType: 'PICTURE',
});
}
if (introduceId) {
_scheduleMediaRequests.push({
contentType: 'INTRO',
mediaContent: introduceId,
mediaType: 'RESOURCE_ID',
});
}
const _stageList = stageList.map((item, index) => {
delete item.contentVOList;
delete item.check;
delete item.type;
delete item.liveFailure;
item.sequence = index + 1;
return item;
});
const _helpStoreUserIds = helpStoreUserIds.map((item) => {
return item.checkedId;
});
const _assignList = assignList.map((item) => {
return {
assignId: item.checkedId,
assignType: item.checkedType,
};
});
const commonParams = {
assignList: _assignList,
createId,
endTime,
helpStoreUserIds: _helpStoreUserIds, // 协同者集合
issueState: issue, // 是否发布
learnType, // 学习模式
scheduleMediaRequests: _scheduleMediaRequests,
startTime,
storeId: User.getStoreId(),
taskName,
timeType,
percentCompleteLive,
percentCompletePicture,
percentCompleteVideo,
introduceId,
trainingStageList: _stageList,
};
if (type === 'edit') {
TaskCenterService.updateTrainingTask({ ...commonParams, id: taskId }).then((res) => {
message.success('保存成功');
RCHistory.goBack();
});
} else {
TaskCenterService.createTrainingTask(commonParams).then((res) => {
message.success('保存成功');
RCHistory.goBack();
});
}
Bus.trigger('getTrainingTaskPage');
Bus.trigger('getStoreTaskNum');
}
// 保存
function handleSubmit(issue) {
setStartCheck(true);
const { taskName, assignList, timeType, startTime, endTime } = basicInfo;
if (!taskName) {
activeStep === 'TRAIN_CONTENT' && setActiveStep('BASIC_INFO');
return message.warning('请输入培训任务名称');
}
if (timeType === 'VALIDITY') {
if (!startTime) {
return message.warning('请选择开始时间');
}
if (!endTime) {
return message.warning('请选择结束时间');
}
if ((type === 'add' || taskState === 'UN_START') && startTime < new Date().getTime()) {
return message.warning('开始时间不能早于现在');
}
if (!endTime > new Date().getTime()) {
return message.warning('结束时间不能早于现在');
}
}
if (assignList.length === 0) {
activeStep === 'TRAIN_CONTENT' && setActiveStep('BASIC_INFO');
return message.warning('请选择指派学员');
}
if (stageList.length === 0) {
activeStep === 'BASIC_INFO' && setActiveStep('TRAIN_CONTENT');
return message.warning('请添加阶段');
}
const stageNameEmpty = stageList.filter((item) => !item.stageName);
if (stageNameEmpty.length > 0) {
activeStep === 'BASIC_INFO' && setActiveStep('TRAIN_CONTENT');
return message.warning('请输入阶段名称');
}
const stageNameArr = stageList.map((item) => item.stageName);
const stageNameSet = new Set(stageNameArr);
if (stageNameSet.size !== stageNameArr.length) {
activeStep === 'BASIC_INFO' && setActiveStep('TRAIN_CONTENT');
return message.warning('阶段名称不能重复');
}
if (percentCompleteLive === '' || percentCompleteVideo === '' || percentCompletePicture === '') {
activeStep === 'BASIC_INFO' && setActiveStep('TRAIN_CONTENT');
return message.warning('请输入完成标准');
}
if (basicInfo.introduce) {
Upload.uploadTextToOSS(
basicInfo.introduce,
`${randomString()}.txt`,
(introduceId) => {
submitRemote(introduceId, issue);
},
() => {
activeStep === 'TRAIN_CONTENT' && setActiveStep('BASIC_INFO');
message.warning('上传培训目的失败');
}
);
} else {
submitRemote(null, issue);
}
}
// 判断基本信息是否修改
function isEqualBasicInfo() {
return Object.entries(originBasicInfo).toString() === Object.entries(basicInfo).toString();
}
// 判断培训内容是否修改
function isEqualStageList() {
return JSON.stringify(originStageList) === JSON.stringify(stageList);
}
// 判断完成标准是否修改
function isFinishedStandard() {
return (
percentCompleteLive === originPercentCompleteLive &&
percentCompleteVideo === originPercentCompleteVideo &&
percentCompletePicture === originPercentCompletePicture
);
}
// 返回、取消
function handleGoBack() {
if (!isEqualBasicInfo() || !isEqualStageList() || !isFinishedStandard()) {
Modal.confirm({
title: '确认要返回吗?',
content: '返回后,本次编辑的内容将不被保存。',
okText: '确认返回',
cancelText: '留在本页',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
window.RCHistory.goBack();
},
});
} else {
window.RCHistory.goBack();
}
}
function handleChangeBasicInfo(field, value) {
if (field === 'coverUrl') {
setBasicInfo({
...basicInfoRef.current,
coverUrl: value.fileUrl,
coverId: value.resourceId,
});
} else if (field === 'trainDate') {
// 固定培训时间,设置起始
setBasicInfo({
...basicInfoRef.current,
startTime: value && value[0]?.valueOf(),
endTime: value && value[1]?.valueOf(),
});
} else {
setBasicInfo({
...basicInfoRef.current,
[field]: value,
});
}
}
function handleChangeStageInfo(field, value) {
if (field === 'stageList') {
setStageList(value);
} else {
switch (field) {
case 'percentCompleteLive':
setPercentCompleteLive(value);
break;
case 'percentCompleteVideo':
setPercentCompleteVideo(value);
break;
case 'percentCompletePicture':
setPercentCompletePicture(value);
break;
default:
break;
}
}
}
return (
<div className='page add-train-task'>
<Breadcrumbs navList={type === 'add' ? '新建培训任务' : '编辑培训任务'} goBack={handleGoBack} />
<div className='box'>
<div className='show-tips'>
<ShowTips message='请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利' />
</div>
<div className='header-tab'>
<span className='tab-title' onClick={() => setActiveStep('BASIC_INFO')}>
<span className={`step-icon ${activeStep === 'BASIC_INFO' ? 'active-icon' : 'default-icon'}`}>1</span>
<span style={{ position: 'relative' }}>
<span className={`${activeStep === 'BASIC_INFO' && 'active-text'}`}>基本信息</span>
{activeStep === 'BASIC_INFO' && <span className='active-line'></span>}
</span>
</span>
<span className='next-arrow'></span>
<span className='tab-title' onClick={() => setActiveStep('TRAIN_CONTENT')}>
<span className={`step-icon ${activeStep === 'TRAIN_CONTENT' ? 'active-icon' : 'default-icon'}`}>2</span>
<span style={{ position: 'relative' }}>
<span className={`${activeStep === 'TRAIN_CONTENT' && 'active-text'}`}>培训内容</span>
{activeStep === 'TRAIN_CONTENT' && <span className='active-line'></span>}
</span>
</span>
</div>
{activeStep === 'BASIC_INFO' && <BasicInfo basicInfo={basicInfo} startCheck={startCheck} onChange={handleChangeBasicInfo} />}
{activeStep === 'TRAIN_CONTENT' && (
<TrainContent
stageList={stageList}
basicInfo={basicInfo}
startCheck={startCheck}
percentCompleteLive={percentCompleteLive}
percentCompleteVideo={percentCompleteVideo}
percentCompletePicture={percentCompletePicture}
onChange={handleChangeStageInfo}
/>
)}
</div>
{renderFooter()}
</div>
);
}
export default AddTrainTask;
.add-train-task {
.header-tab {
position: relative;
padding: 20px 0 12px 0;
text-align: center;
font-size: 16px;
color: #666666;
line-height: 22px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.tab-title {
position: relative;
cursor: pointer;
&:first-child {
margin-right: 44px;
}
.step-icon {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
font-size: 12px;
text-align: center;
line-height: 14px;
margin-right: 8px;
&.default-icon {
border: 1px solid #999999;
color: #999999;
}
&.active-icon {
background-color: #2966ff;
border: 1px solid #2966ff;
color: #ffffff;
}
}
.active-text {
color: #2966ff;
}
.active-line {
position: absolute;
width: 32px;
height: 2px;
background: #2966ff;
top: 34px;
left: 16px;
z-index: 999;
}
}
&::after {
content: ' ';
height: 10px;
width: 10px;
border-width: 2px 2px 0 0;
border-color: #999999;
border-style: solid;
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
position: absolute;
top: 50%;
left: calc(50% - 5px);
}
}
.footer {
position: fixed;
left: 196px;
bottom: 0;
height: 58px;
width: ~'calc(100% - 218px)';
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 72px;
background: #fff;
border-top: 1px solid #e8e8e8;
z-index: 9999;
.ant-btn {
margin-left: 10px;
}
}
}
/*
* @Author: yuananting
* @Date: 2021-07-28 11:25:58
* @LastEditors: yuananting
* @LastEditTime: 2021-08-18 12:36:21
* @Description: 任务中心-培训任务
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState } from 'react';
import TrainFilter from './components/TrainFilter';
import { Route, withRouter } from 'react-router-dom';
import TrainList from './components/TrainList';
import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
import DataCenter from '../data-center/Index';
import User from '@/common/js/user';
import Bus from '@/core/bus';
function TrainTaskPage(props) {
const [trainListData, setTrainListData] = useState([]); // 培训任务列表
const [query, setQuery] = useState({
current: 1,
endTime: null,
issueState: 'ALL', // 发布状态
myAssist: false, // 是否由我协同
size: 10,
sortMap: { UPDATED: 'SORT_DESC' }, // 排序(默认更新时间倒序)
startTime: null,
storeId: User.getStoreId(),
storeUserId: User.getStoreUserId(),
taskName: '',
});
const [totalCount, setTotalCount] = useState(0); // 总数
const [storeTaskNum, setStoreTaskNum] = useState({});
const { match } = props;
useEffect(() => {
getTrainingTaskPage();
}, [query]);
useEffect(() => {
getStoreTaskNum();
initPageData();
return () => {
removePageData();
};
}, []);
useEffect(() => {}, []);
const initPageData = () => {
Bus.bind('getTrainingTaskPage', (filterData) => {
getTrainingTaskPage(filterData);
});
Bus.bind('getStoreTaskNum', getStoreTaskNum);
};
const removePageData = () => {
Bus.unbind('getTrainingTaskPage', getTrainingTaskPage);
Bus.unbind('getStoreTaskNum', getStoreTaskNum);
};
// 获取计划列表
function getTrainingTaskPage(filterData) {
const _query = { ...query, ...filterData };
if (_query.issueState === 'ALL') {
delete _query.issueState;
}
TaskCenterService.getTrainingTaskPage(_query).then((res) => {
const {
result: { records = [], total },
} = res;
setTrainListData(records);
setTotalCount(total);
});
}
// 获取学院任务数量
function getStoreTaskNum() {
TaskCenterService.getStoreTaskNum({ storeId: User.getStoreId() }).then((res) => {
setStoreTaskNum(res.result);
});
}
// 搜索条件修改
function queryChange(_query) {
setQuery({ ...query, ..._query });
}
return (
<div className='page'>
<div className='content-header'>培训任务</div>
<div className='box'>
<TrainFilter onChange={queryChange} />
<TrainList trainListData={trainListData} storeTaskNum={storeTaskNum} query={query} totalCount={totalCount} onChange={queryChange} />
</div>
<Route path={`${match.url}/data/:taskId`} component={DataCenter} />
</div>
);
}
export default withRouter(TrainTaskPage);
/*
* @Author: yuananting
* @Date: 2021-07-29 14:32:24
* @LastEditors: yuananting
* @LastEditTime: 2021-08-17 14:50:30
* @Description: 任务中心-培训任务-新建-基本信息
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState } from 'react';
import { Form, Button, Input, Space, DatePicker, Radio, Tag, message, Tooltip } from 'antd';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import Upload from '@/core/upload';
import GraphicsEditor from '@/modules/course-manage/components/GraphicsEditor';
import ImgClipModal from '@/components/ImgClipModal';
import moment from 'moment';
import './BasicInfo.less';
import ChooseAssignorModal from '../modal/ChooseAssignorModal';
import ChooseCollaboratorModal from '../modal/ChooseCollaboratorModal';
import WWOpenDataCom from '@/components/WWOpenDataCom';
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
function BasicInfo(props) {
const taskState = window.getParameterByName('taskState');
const type = window.getParameterByName('type');
const originTimeType = window.getParameterByName('timeType');
const { basicInfo, startCheck } = props;
const { createId, taskName, coverUrl, helpStoreUserIds, timeType, startTime, endTime, learnType, assignList, introduce, loadintroduce } = basicInfo;
const depAssignList = assignList.filter((item) => item.checkedType !== 'CUSTOMER');
const userAssignList = assignList.filter((item) => item.checkedType === 'CUSTOMER');
const [imageFile, setImageFile] = useState(null); // 需要被截取的图片
const [showSelectFileModal, setShowSelectFileModal] = useState(false);
const [imgClipVisible, setImgClipVisible] = useState(false);
// 当前是否使用的是默认图片
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const isDefaultCover = coverUrl === defaultCover;
const [assignorModalVisible, setAssignorModalVisible] = useState(false); // 指派学员弹窗显隐
const [collaboratorModalVisible, setCollaboratorModalVisible] = useState(false); // 协同者弹窗显隐
// 使用默认封面图
function handleResetCoverUrl() {
message.success('已替换为默认图');
props.onChange('coverUrl', { fileUrl: defaultCover });
}
// 从云盘选择封面
function handleSelectCover(file) {
if(file){
setImgClipVisible(true);
setImageFile(file);
}else{
message.warning('请选择文件!')
}
}
function getSignature(blob, fileName) {
Upload.uploadBlobToOSS(blob, 'cover' + new Date().valueOf(), null, 'signInfo').then((signInfo) => {
const { fileUrl, resourceId } = signInfo;
setImgClipVisible(false);
setShowSelectFileModal(false);
props.onChange('coverUrl', { fileUrl, resourceId });
});
}
// 禁选日期
function disabledDate(current) {
return current && current < moment().startOf('day');
}
// 禁选时间
function disabledRangeTime(date, type) {
if (moment(date).isSame(moment(), 'day')) {
return {
disabledHours: () => {
const hours = [];
for (let i = 0; i < moment().hour(); i++) {
hours.push(i);
}
return hours;
},
disabledMinutes: () => {
const currentMinute = moment().minute();
const currentHour = moment(date).hour();
const minutes = [];
if (currentHour === moment().hour()) {
for (let i = 0; i < currentMinute; i++) {
minutes.push(i);
}
}
return minutes;
},
};
}
return {
disabledHours: () => [],
disabledMinutes: () => [],
disabledSeconds: () => [],
};
}
function confirmAddCollaborator(data) {
props.onChange('helpStoreUserIds', data);
}
function confirmAddAssignor(data) {
props.onChange('assignList', data);
setAssignorModalVisible(false);
}
function removeSelectedCollaborator(tag) {
const _helpStoreUserIds = helpStoreUserIds.filter((item) => item.checkedId !== tag.checkedId);
props.onChange('helpStoreUserIds', _helpStoreUserIds);
}
function removeCheckedAssignor(tag) {
const _assignList = assignList.filter((item) => item.checkedId !== tag.checkedId);
props.onChange('assignList', _assignList);
}
function checkTime() {
if (!startTime && !endTime) {
return '请选择培训时间';
}
if (!startTime) {
return '请选择开始时间';
}
if (!endTime) {
return '请选择结束时间';
}
if ((type === 'add' || taskState === 'UN_START') && startTime < new Date().getTime()) {
return '开始时间不能早于现在';
}
if (!endTime > new Date().getTime()) {
return '结束时间不能早于现在';
}
return false;
}
return (
<div className='basic-info__form'>
<Form>
<FormItem
label='培训任务名称'
required
validateStatus={startCheck && (!taskName || taskName.length > 20) ? 'error' : ''}
help={startCheck && ((!taskName && '请输入培训任务名称') || (taskName.length > 20 && '任务名称最多20字'))}>
<Input
value={taskName}
placeholder='请输入培训任务名称(20字以内)'
maxLength={20}
style={{ width: 300 }}
onChange={(e) => props.onChange('taskName', e.target.value)}
/>
</FormItem>
<FormItem label='封面图'>
<div className='cover__wrap'>
<div className='opt-box'>
<Button
onClick={() => {
setShowSelectFileModal(true);
}}>
上传封面
</Button>
<span className={`default-btn ${isDefaultCover ? 'disabled' : ''}`} onClick={handleResetCoverUrl}>
使用默认图
</span>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
<div className='img-box'>
{isDefaultCover && <span className='default-tag'>默认图</span>}
<img src={coverUrl} alt='' />
</div>
</div>
</FormItem>
<FormItem label='培训时间'>
<div className='duration__wrap'>
<Radio.Group
value={timeType}
disabled={originTimeType === 'VALIDITY' && taskState === 'STARTING'}
onChange={(e) => {
props.onChange('timeType', e.target.value);
}}>
<Space direction='vertical' size={16}>
<Radio value='FOREVER'>
永久有效<span className='tips'>设置为“永久有效”,发布后任务开始生效,取消发布后失效</span>
</Radio>
<Radio value='VALIDITY'>
固定时间段
{timeType === 'VALIDITY' && (
<div className='picker-box'>
<FormItem validateStatus={startCheck && checkTime() ? 'error' : ''} help={startCheck && checkTime()}>
<RangePicker
style={{ width: 320 }}
showTime={{ defaultValue: [moment().add(10, 'minutes'), moment().add(10, 'minutes')] }}
ranges={{
7: [
type === 'edit' && taskState === 'STARTING' ? moment(startTime) : moment().add(10, 'minute'),
moment().add(6, 'day').endOf('day'),
],
1个月: [
type === 'edit' && taskState === 'STARTING' ? moment(startTime) : moment().add(10, 'minute'),
moment().add(1, 'month').endOf('day'),
],
3个月: [
type === 'edit' && taskState === 'STARTING' ? moment(startTime) : moment().add(10, 'minute'),
moment().add(3, 'month').endOf('day'),
],
}}
disabledDate={disabledDate}
disabledTime={disabledRangeTime}
disabled={[type === 'edit' && taskState === 'STARTING', false]}
value={[startTime ? moment(Number(startTime)) : null, endTime ? moment(Number(endTime)) : null]}
format='YYYY-MM-DD HH:mm'
onChange={(date) => {
props.onChange('trainDate', date);
}}
/>{' '}
</FormItem>
</div>
)}
</Radio>
</Space>
</Radio.Group>
</div>
</FormItem>
<FormItem label='学习模式' required extra={<div className='learning-model-tips'>提示:任务开始后学习模式将不允许更换</div>}>
<div className='learning-model__wrap'>
<Radio.Group value={learnType} disabled={taskState === 'STARTING'} onChange={(e) => props.onChange('learnType', e.target.value)}>
<Space direction='vertical' size={16}>
<Radio value='FREEDOM'>自由学习</Radio>
<Radio value='ORDER'>
闯关学习
<Tooltip title='学员必须按顺序学习'>
<i
className='icon iconfont'
style={{
marginLeft: '5px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: 'normal',
}}>
&#xe61d;
</i>
</Tooltip>
</Radio>
</Space>
</Radio.Group>
</div>
</FormItem>
<FormItem
label={
<span>
指派学员
<Tooltip title='选择需要培训的人员'>
<i
className='icon iconfont'
style={{
marginLeft: '5px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: 'normal',
}}>
&#xe61d;
</i>
</Tooltip>
</span>
}
required
validateStatus={startCheck && assignList.length === 0 ? 'error' : ''}
help={startCheck && assignList.length === 0 && '请选择指派学员'}>
<Button
style={{ display: 'block' }}
onClick={() => {
setAssignorModalVisible(true);
}}>
添加指派学员
</Button>
{assignList.length > 0 && (
<Space size={'12'} direction={'vertical'} className='select-obj'>
{depAssignList.length > 0 && (
<div className='obj-type-container'>
<div className='type-title'>已选组织:</div>
<div className='tag-box'>
{_.map(depAssignList, (tag) => {
return (
<Tag
key={tag.checkedId}
closeIcon={<span className='icon iconfont close-icon'>&#xe626;</span>}
onClose={() => removeCheckedAssignor(tag)}
closable>
<WWOpenDataCom type='departmentName' openid={tag.checkedName} />
</Tag>
);
})}
</div>
</div>
)}
{userAssignList.length > 0 && (
<div className='obj-type-container'>
<div className='type-title'>已选学员:</div>
<div className='tag-box'>
{_.map(userAssignList, (tag) => {
return (
<Tag
key={tag.checkedId}
closeIcon={<span className='icon iconfont close-icon'>&#xe626;</span>}
onClose={() => removeCheckedAssignor(tag)}
closable>
<WWOpenDataCom type='userName' openid={tag.checkedName} />
</Tag>
);
})}
</div>
</div>
)}
</Space>
)}
</FormItem>
<FormItem
label={
<span>
协同人员
<Tooltip title='选择员工协同完成任务指派和督学工作'>
<i
className='icon iconfont'
style={{
marginLeft: '5px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: 'normal',
}}>
&#xe61d;
</i>
</Tooltip>
</span>
}>
<Button
style={{ display: 'block' }}
onClick={() => {
setCollaboratorModalVisible(true);
}}>
添加协同者
</Button>
{helpStoreUserIds.length > 0 && (
<div className='select-obj'>
{_.map(helpStoreUserIds, (tag) => {
return (
<Tag
key={tag.checkedId}
closeIcon={<span className='icon iconfont close-icon'>&#xe626;</span>}
className={tag.checkedId === createId && 'disabled'}
onClose={() => removeSelectedCollaborator(tag)}
closable>
<WWOpenDataCom type='userName' openid={tag.checkedName} />
</Tag>
);
})}
</div>
)}
</FormItem>
<FormItem label='培训目的'>
{(type === 'add' || loadintroduce) && (
<GraphicsEditor
maxLimit={1000}
id='intro'
isIntro={true}
placeholder='请输入培训目的'
detail={{
content: introduce,
}}
onChange={(val) => {
props.onChange('introduce', val);
}}
/>
)}
</FormItem>
</Form>
{showSelectFileModal && (
<SelectPrepareFileModal
key='basic'
operateType='select'
multiple={false}
accept='image/jpeg,image/png,image/jpg'
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectFileModal}
onClose={() => {
setShowSelectFileModal(false);
}}
onSelect={handleSelectCover}
/>
)}
{imgClipVisible && (
<ImgClipModal
visible={imgClipVisible}
imgUrl={imageFile.ossUrl}
onConfirm={getSignature}
onClose={() => {
setImgClipVisible(false);
}}
/>
)}
{assignorModalVisible && (
<ChooseAssignorModal
currentAssignorList={assignList}
visible={assignorModalVisible}
onClose={() => {
setAssignorModalVisible(false);
}}
onConfirm={(data) => {
confirmAddAssignor(data);
}}
/>
)}
{collaboratorModalVisible && (
<ChooseCollaboratorModal
createId={createId}
currentCollaboratorList={helpStoreUserIds}
visible={collaboratorModalVisible}
onClose={() => {
setCollaboratorModalVisible(false);
}}
onConfirm={(data) => {
confirmAddCollaborator(data);
}}
/>
)}
</div>
);
}
export default BasicInfo;
.basic-info__form {
margin-bottom: 138px;
.cover {
display: flex;
margin-top: 24px;
&__wrap {
position: relative;
.img-box {
position: relative;
margin-top: 8px;
width: 300px;
height: 170px;
.default-tag {
border-radius: 2px;
background: #d6d6d6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
}
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.opt-box {
.default-btn {
margin-left: 12px;
color: #2966ff;
cursor: pointer;
&.disabled {
color: #ccc;
cursor: not-allowed;
pointer-events: none;
}
}
.tips {
margin-top: 8px;
font-size: 14px;
color: #999;
}
}
}
}
.duration {
&__wrap {
.tips {
color: #999999;
display: inline-block;
margin-left: 16px;
}
.picker-box {
display: inline-block;
margin-left: 16px;
.ant-form-item {
margin-bottom: 0 !important;
}
}
}
}
.select-obj {
width: 600px;
max-height: 90px;
overflow-y: scroll;
padding: 12px;
border-radius: 4px;
margin-top: 10px;
border: 1px solid #e8e8e8;
color: #666666;
.obj-type-container {
display: flex;
overflow-wrap: normal;
.type-title {
width: 70px;
flex-shrink: 0;
}
}
.close-icon {
font-size: 14px;
color: #cccccc;
}
.disabled {
color: #ccc !important;
cursor: not-allowed !important;
pointer-events: none !important;
}
}
.learning-model-tips {
margin-top: 8px;
color: #999999;
line-height: 20px;
}
.ant-form-item {
margin-bottom: 24px !important;
.ant-form-item-label > label {
width: 108px;
justify-content: flex-end;
}
&:not(:first-child) .ant-form-item-label > label {
height: 22px !important;
}
.ant-form-item-control-input {
min-height: 22px !important;
}
.ant-tag {
border: 1px solid #cccccc;
color: #666666;
margin-bottom: 10px;
font-size: 14px;
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-03 10:47:59
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 22:04:31
* @Description: 编辑培训任务-失效课程
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { List, message, Modal } from 'antd';
import './ExpiredCourseList.less';
import ENUM from '../../enum';
import moment from 'moment';
import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
function ExpiredCourseList(props) {
const [expiredCourseList, setExpiredCourseList] = useState([]); // 失效课程
useEffect(() => {
getTrainingCourseAutoCancel();
}, []);
// 获取培训任务失效的课程
function getTrainingCourseAutoCancel() {
TaskCenterService.getTrainingCourseAutoCancel({ taskId: props.taskId }).then((res) => {
setExpiredCourseList(res.result);
});
}
// 移除未成功开课课程
function deLExpiredCourse(contentId) {
const { taskId } = props;
Modal.confirm({
content: '你确定要删除该数据内容吗?',
okText: '确定',
cancelText: '取消',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
const params = {
taskId,
contentIdList: [contentId],
};
TaskCenterService.delTaskCancelContent(params).then((res) => {
message.success('删除成功');
getTrainingCourseAutoCancel();
});
},
});
}
return (
<div>
{expiredCourseList.length > 0 && (
<div className='expired-list-page'>
<div className='tip'>为了不影响学员学习,「未成功开课」的课程已从任务中移出,具体课程如下所示:</div>
<List
dataSource={expiredCourseList}
renderItem={(item) => (
<List.Item>
<div className='item-detail'>
<span className='icon iconfont'>&#xe80b;</span>
<span className='content-status'>未成功开课</span>
<span className='stage-name'>{item.stageName}</span>
<span className='content-name'>
<img src={ENUM.LearningContentIcon[item.courseType]} />
<span>{item.courseName}</span>
</span>
<span className='teacher-name'>{item.teacherName}</span>
<span className='split'>|</span>
<span className='course-time'>
{moment(item.startTime).format('YYYY-MM-DD HH:mm')}~{moment(item.endTime).format('HH:mm')}
</span>
<span
className='del-btn'
onClick={() => {
deLExpiredCourse(item.courseId);
}}>
删除记录
</span>
</div>
</List.Item>
)}
/>
</div>
)}
</div>
);
}
export default ExpiredCourseList;
.expired-list-page {
.tip {
color: #999999;
line-height: 20px;
margin-top: 8px;
margin-bottom: 16px;
}
.ant-list-item {
padding: 16px;
border: none !important;
&:nth-child(even) {
background: #ffffff;
}
&:nth-child(odd) {
background: #f7f8f9;
}
.item-detail {
display: inline-flex;
width: 100%;
height: 20px;
.icon {
font-size: 14px;
color: #ff4f4f;
margin-right: 4px;
}
.content-status {
color: #999999;
margin-right: 24px;
}
.stage-name {
color: #333333;
margin-right: 12px;
}
.content-name {
color: #333333;
margin-right: 12px;
vertical-align: middle;
> * {
vertical-align: middle;
}
img {
width: 20px;
height: 20px;
margin-right: 12px;
}
}
.teacher-name,
.course-time {
color: #999999;
}
.split {
margin-left: 12px;
margin-right: 12px;
color: #999999;
}
.del-btn {
margin-left: auto;
color: #2966ff;
cursor: pointer;
}
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-01 17:28:30
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 19:53:06
* @Description: 新建培训任务-关联课程抽屉
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from 'react';
import _ from 'underscore';
import { Radio, Tabs, Drawer, Input, message, Button, Tooltip } from 'antd';
import { PageControl, XMTable } from '@/components';
import college from '@/common/lottie/college';
import CourseService from '@/domains/course-domain/CourseService';
import User from '@/common/js/user';
import Service from '@/common/js/service';
import ENUM from '../../enum';
import './RelatedCourseDrawer.less';
const { Search } = Input;
const { TabPane } = Tabs;
class RelatedCourseDrawer extends Component {
constructor(props) {
super(props);
this.state = {
// 直播课列表相关参数
liveDataSource: [],
liveSize: 10,
liveQuery: {
current: 1,
},
liveTotalCount: 0,
selectLive: [], //弹窗内已选择的直播课程
currentLiveCourseListData: [], //页面中已关联的直播课程
// 线上课列表相关参数(内外部课程)
videoCourseDivision: 'internal',
videoDataSource: {
external: [],
internal: [],
},
videoSize: {
external: 10,
internal: 10,
},
videoSearchName: {
external: '',
internal: '',
},
videoSearchDefalt: '',
videoQuery: {
external: {
current: 1,
},
internal: {
current: 1,
},
},
videoTotalCount: {
external: 0,
internal: 0,
},
selectVideo: {
external: [],
internal: [],
}, //弹窗内已选择的线上课程
currentVideoCourseListData: {
external: [],
internal: [],
}, //页面中已关联的线上课程
pictureDataSource: [],
pictureSize: 10,
pictureQuery: {
current: 1,
},
pictureTotalCount: 0,
selectPicture: [], //弹窗内已选择的线上课程
currentPictureCourseListData: [], //页面中已关联的线上课程
activeKey: 'live',
currentTaskContentData: props.data[props.selectedStageIndex].contentList || [],
};
}
componentDidMount() {
this.handleFetchLiveDataList();
this.handleFetchVideoDataList();
this.handleFetchPictureDataList();
}
// 获取直播课列表
handleFetchLiveDataList = () => {
const { liveQuery, liveSize } = this.state;
const _data = [...this.props.data];
let currentLiveCourseListData = [];
_data.map((item) => {
item.contentList.map((childItem, childIndex) => {
if (childItem.courseType === 'LIVE') {
currentLiveCourseListData.push(childItem.contentId);
}
return childItem;
});
return item;
});
const params = {
...liveQuery,
size: liveSize,
excludeCourseIdList: currentLiveCourseListData,
};
CourseService.getLiveCloudCourseBasePage(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
liveDataSource: records,
liveTotalCount: Number(total),
currentLiveCourseListData,
});
});
};
// 获取线上课列表
handleFetchVideoDataList = () => {
const { videoQuery, videoSize, videoDataSource, videoTotalCount, videoCourseDivision } = this.state;
const _data = [...this.props.data];
console.log('_data', _data);
let currentVideoCourseListData = [];
_data.map((item, index) => {
item.contentList.map((childItem, childIndex) => {
console.log('childItem', childItem);
if (childItem.courseType === 'VOICE') {
currentVideoCourseListData.push(childItem.contentId);
}
return childItem;
});
return item;
});
const params = {
...videoQuery[videoCourseDivision],
size: videoSize[videoCourseDivision],
courseDivision: videoCourseDivision === 'internal' ? 'INTERNAL' : 'EXTERNAL',
excludeCourseIdList: currentVideoCourseListData,
};
CourseService.videoScheduleBasePage(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
videoDataSource: {
...videoDataSource,
[videoCourseDivision]: records,
},
videoTotalCount: {
...videoTotalCount,
[videoCourseDivision]: Number(total),
},
currentVideoCourseListData,
});
});
};
// 获取图文课列表
handleFetchPictureDataList = () => {
const { pictureQuery, pictureSize } = this.state;
const _data = [...this.props.data];
let currentPictureCourseListData = [];
_data.map((item, index) => {
item.contentList.map((childItem, childIndex) => {
if (childItem.courseType === 'PICTURE') {
currentPictureCourseListData.push(childItem.contentId);
}
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, videoCourseDivision, videoSearchName } = this.state;
videoQuery[videoCourseDivision].courseName = value;
videoQuery[videoCourseDivision].current = 1;
this.setState({
...videoQuery,
videoSearchDefalt: value,
videoSearchName: {
...videoSearchName,
[videoCourseDivision]: value,
},
});
};
handleChangLiveCourseName = (value) => {
const { liveQuery } = this.state;
liveQuery.courseName = value;
liveQuery.current = 1;
this.setState({
liveQuery,
});
};
handleChangPictureCourseName = (value) => {
const { pictureQuery } = this.state;
pictureQuery.courseName = value;
pictureQuery.current = 1;
this.setState({
pictureQuery,
});
};
onShowLiveSizeChange = (current, size) => {
if (current === size) {
return;
}
this.setState(
{
liveSize: size,
},
() => {
this.handleFetchLiveDataList();
}
);
};
onShowVideoSizeChange = (current, size) => {
if (current === size) {
return;
}
this.setState(
{
videoSize: size,
},
() => {
this.handleFetchLiveDataList();
}
);
};
onShowPictureSizeChange = (current, size) => {
if (current === size) {
return;
}
this.setState(
{
pictureSize: size,
},
() => {
this.handleFetchPictureDataList();
}
);
};
// 请求表头
parseCourseColumns = (type) => {
const columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip title='仅显示未关联课程,已关联课程不支持重复选择'>
<i className='icon iconfont' style={{ marginLeft: '5px', cursor: 'pointer', color: '#bfbfbf', fontSize: '14px', fontWeight: '400' }}>
&#xe61d;
</i>
</Tooltip>
</span>
),
key: 'course',
dataIndex: 'course',
width: '30%',
render: (val, record) => {
if (type === 'live') {
let hasCover = false;
return (
<div className='course-info'>
{record.courseMediaVOS.map((item) => {
if (item.contentType === 'COVER') {
hasCover = true;
return <img className='course-cover' src={item.mediaUrl} alt='' />;
}
return null;
})}
<If condition={!hasCover}>
<img className='course-cover' src={'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'} alt='' />
</If>
<div className='course-name'>{record.courseName}</div>
</div>
);
} else {
const { coverUrl } = record;
return (
<div className='course-info'>
<img
className='course-cover'
src={
coverUrl || (type === 'video' ? 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png' : 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png')
}
alt=''
/>
<div className='course-name'>{record.courseName}</div>
</div>
);
}
},
},
type === 'live'
? {
title: '课程状态',
key: 'courseState',
dataIndex: 'courseState',
width: '25%',
render: (val, record) => {
return (
<div className='course-state'>
<span className='status-point' style={{ backgroundColor: ENUM.trainStatus[val || 'UN_START'].color }}></span>
<span>{ENUM.trainStatus[val || 'UN_START'].text}</span>
</div>
);
},
}
: {},
type === 'live'
? {
title: '上课时间',
key: 'courseTime',
dataIndex: 'courseTime',
width: '25%',
render: (val, record) => {
return (
<div>
<div>{formatDate('YYYY-MM-DD', record.startTime)}</div>
<div>
{formatDate('H:i', record.startTime)}~{formatDate('H:i', record.endTime)}
</div>
</div>
);
},
}
: type === 'video'
? {
title: '课节数',
key: 'courseChapterNum',
dataIndex: 'courseChapterNum',
width: '20%',
render: (val, record) => {
return <span>{val || 1}</span>;
},
}
: {
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>;
},
},
];
return columns;
};
selectLiveList = (record, selected) => {
const { selectVideo, currentTaskContentData, 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 + currentTaskContentData.length + selectVideo.internal.length + selectVideo.external.length + selectPicture.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({ selectLive: _list });
};
selectVideoList = (record, selected) => {
const { selectVideo, currentTaskContentData, selectLive, selectPicture, videoCourseDivision } = this.state;
let { [videoCourseDivision]: selectList } = selectVideo;
let otherVideoCourseDivision = videoCourseDivision === 'internal' ? 'external' : 'internal';
let _list = [];
if (selected || !_.find(selectList, (item) => item.id === record.id)) {
_list = _.uniq(selectList.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectList, (item) => item.id === record.id);
}
if (_list.length + selectVideo[otherVideoCourseDivision]?.length + currentTaskContentData.length + selectLive.length + selectPicture.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({
selectVideo: {
...selectVideo,
[videoCourseDivision]: _list,
},
});
};
selectPictureList = (record, selected) => {
const { selectVideo, currentTaskContentData, 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 + currentTaskContentData.length + selectLive.length + selectVideo.internal.length + selectVideo.external.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({ selectPicture: _list });
};
clearSelectCourse = () => {
this.setState({
selectLive: [],
selectVideo: {
internal: [],
external: [],
},
selectPicture: [],
});
};
handleSelectVideo = (selectVideo) => {
return selectVideo.map((item) => {
let _item = {};
_item.courseId = item.id;
_item.courseType = 'VOICE';
_item.courseName = item.courseName;
_item.courseChapterNum = item.courseChapterNum;
return _item;
});
};
handleSelectLive = (selectLive) => {
return selectLive.map((item, index) => {
let _item = {};
_item.courseId = item.liveCourseId;
_item.courseType = 'LIVE';
_item.courseName = item.courseName;
_item.courseState = item.courseState;
return _item;
});
};
videoCourseDivisionChange = (e) => {
const { videoSearchName } = this.state;
this.setState(
{
videoCourseDivision: e.target.value,
videoSearchDefalt: videoSearchName[e.target.value],
},
() => {
this.handleFetchVideoDataList();
}
);
};
handleSelectPicture = (selectPicture) => {
return selectPicture.map((item, index) => {
let _item = {};
_item.courseId = item.id;
_item.courseType = 'PICTURE';
_item.courseName = item.courseName;
return _item;
});
};
renderTipFooter = () => {
const { activeKey } = this.state;
let href = '';
switch (activeKey) {
case 'live':
href = (
<a
target='_blank'
rel='noopener noreferrer'
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'
rel='noopener noreferrer'
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'
rel='noopener noreferrer'
className='link-create-course'
href={window.location.origin + window.location.pathname + '#/create-graphics-course?type=add'}
onClick={this.props.onClose}>
没有找到需要的图文课?<span>去创建</span>
</a>
);
break;
default:
break;
}
return href;
};
render() {
const {
liveDataSource,
liveSize,
liveQuery,
liveTotalCount,
selectLive,
videoDataSource,
videoSize,
videoQuery,
videoSearchDefalt,
videoTotalCount,
selectVideo,
currentTaskContentData,
selectPicture,
pictureDataSource,
pictureSize,
pictureQuery,
pictureTotalCount,
videoCourseDivision,
activeKey,
} = this.state;
return (
<Drawer
title='关联课程'
width={720}
maskClosable={false}
closable={true}
onClose={this.props.onClose}
visible={true}
mask
className='related-course-drawer'>
<div>
<Tabs
type='line'
defaultActiveKey='live'
onChange={(activeKey) => {
this.setState({ activeKey: activeKey });
}}>
<TabPane tab='直播课' key='live'>
<div className='search-container'>
<Search
enterButton={<span className='icon iconfont'>&#xe832;</span>}
placeholder='搜索课程名称'
style={{ width: 200 }}
onChange={(e) => {
this.handleChangLiveCourseName(e.target.value);
}}
onSearch={() => {
this.handleFetchLiveDataList();
}}
/>
</div>
<div className='select-area'>
<div className='select-box'>
<div>
<span className='icon iconfont tip-icon'>&#xe61d;</span>
<span className='select-num'>
已选择{selectVideo.internal.length + selectVideo.external.length + selectLive.length + selectPicture.length}
</span>
</div>
<div>
<span className='clear-btn' onClick={this.clearSelectCourse}>
清空
</span>
</div>
</div>
<div className='related-box'>
该任务已关联{currentTaskContentData.length}个课程,可继续选择{20 - currentTaskContentData.length}
</div>
</div>
<div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.liveCourseId}
dataSource={liveDataSource}
columns={this.parseCourseColumns('live')}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectLive, 'liveCourseId'),
onSelect: (record, selected) => {
this.selectLiveList(record, selected);
},
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));
}
if (
_list.length + currentTaskContentData.length + selectVideo.internal.length + selectVideo.external.length + selectPicture.length >
20
) {
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength =
_list.length + currentTaskContentData.length + selectVideo.internal.length + selectVideo.external.length + +selectPicture.length - 20;
_list.splice(_list.length - extraLength, extraLength);
}
this.setState({ selectLive: _list });
},
}}
/>
{liveDataSource.length > 0 && (
<div className='box-footer'>
<PageControl
current={liveQuery.current - 1}
pageSize={liveSize}
size='small'
total={liveTotalCount}
toPage={(page) => {
const _query = { ...liveQuery, current: page + 1 };
this.setState(
{
liveQuery: _query,
},
() => {
this.handleFetchLiveDataList();
}
);
}}
onShowSizeChange={this.onShowLiveSizeChange}
/>
</div>
)}
</div>
</TabPane>
<TabPane tab='线上课' key='video'>
<Radio.Group value={videoCourseDivision} onChange={this.videoCourseDivisionChange} style={{ marginBottom: 16 }}>
<Radio.Button value='internal'>内部课程</Radio.Button>
<Radio.Button value='external'>外部课程</Radio.Button>
</Radio.Group>
<div className='search-container'>
<Search
value={videoSearchDefalt}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
placeholder='搜索课程名称'
style={{ width: 200 }}
onChange={(e) => {
this.handleChangVideoCourseName(e.target.value);
}}
onSearch={() => {
this.handleFetchVideoDataList();
}}
/>
</div>
<div className='select-area'>
<div className='select-box'>
<div>
<span className='icon iconfont tip-icon'>&#xe61d;</span>
<span className='select-num'>
已选择{selectVideo.internal.length + selectVideo.external.length + selectLive.length + selectPicture.length}
</span>
</div>
<div>
<span className='clear-btn' onClick={this.clearSelectCourse}>
清空
</span>
</div>
</div>
<div className='related-box'>
该任务已关联{currentTaskContentData.length}个课程,可继续选择{20 - currentTaskContentData.length}
</div>
</div>
<div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.id}
dataSource={videoDataSource[videoCourseDivision]}
columns={this.parseCourseColumns('video')}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectVideo[videoCourseDivision], 'id'),
onSelect: (record, selected) => {
this.selectVideoList(record, selected);
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
let otherVideoCourseDivision = videoCourseDivision === 'internal' ? 'external' : 'internal';
if (selected) {
_list = _.uniq(selectVideo[videoCourseDivision].concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectVideo[videoCourseDivision], (item) => _.find(changeRows, (data) => data.id === item.id));
}
if (
_list.length +
selectVideo[otherVideoCourseDivision]?.length +
currentTaskContentData.length +
selectLive.length +
selectPicture.length >
20
) {
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength =
_list.length +
selectVideo[otherVideoCourseDivision]?.length +
currentTaskContentData.length +
selectLive.length +
selectPicture.length -
20;
_list.splice(_list.length - extraLength, extraLength);
}
this.setState({
selectVideo: {
...selectVideo,
[videoCourseDivision]: _list,
},
});
},
}}
/>
{videoDataSource[videoCourseDivision].length > 0 && (
<div className='box-footer'>
<PageControl
current={videoQuery[videoCourseDivision].current - 1}
pageSize={videoSize[videoCourseDivision]}
size='small'
total={videoTotalCount[videoCourseDivision]}
toPage={(page) => {
const _query = { ...videoQuery[videoCourseDivision], current: page + 1 };
this.setState(
{
videoQuery: {
...videoQuery,
[videoCourseDivision]: _query,
},
},
() => {
this.handleFetchVideoDataList();
}
);
}}
onShowSizeChange={this.onShowVideoSizeChange}
/>
</div>
)}
</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.internal.length + selectVideo.external.length + selectLive.length + selectPicture.length}
</span>
</div>
<div>
<span className='clear-btn' onClick={this.clearSelectCourse}>
清空
</span>
</div>
</div>
<div className='related-box'>
该任务已关联{currentTaskContentData.length}个课程,可继续选择{20 - currentTaskContentData.length}
</div>
</div>
<div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.id}
dataSource={pictureDataSource}
columns={this.parseCourseColumns('picture')}
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 + currentTaskContentData.length + selectVideo.internal.length + selectVideo.external.length + selectLive.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength =
_list.length + currentTaskContentData.length + selectVideo.internal.length + selectVideo.external.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>
<div className='footer shrink-footer'>
{!(activeKey === 'video' && videoCourseDivision === 'external') && this.renderTipFooter()}
<Button onClick={this.props.onClose}>取消</Button>
<Button
type='primary'
onClick={() =>
this.props.onSelect([
...this.handleSelectVideo(selectVideo.internal),
...this.handleSelectVideo(selectVideo.external),
...this.handleSelectLive(selectLive),
...this.handleSelectPicture(selectPicture),
])
}>
确定
</Button>
</div>
</Drawer>
);
}
}
export default RelatedCourseDrawer;
.related-course-drawer {
.link-create-course {
color: #666666;
font-size: 14px;
width: 638px;
text-align: left;
display: inline-block;
span {
color: #2966ff;
}
}
.search-container {
margin-bottom: 16px;
}
.select-area {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
.select-box {
display: inline-box;
width: 186px;
background: #e9efff;
border-radius: 4px;
padding: 6px 16px;
margin-right: 8px;
display: flex;
justify-content: space-between;
.tip-icon {
color: #2966ff;
font-size: 14px;
margin-right: 4px;
}
.select-num {
color: #666666;
font-size: 14px;
}
.clear-btn {
text-align: right;
color: #5289fa;
font-size: 14px;
}
}
.related-box {
padding: 6px 16px;
background: #e9efff;
border-radius: 4px;
flex: 1;
color: #666666;
font-size: 14px;
}
}
.course-info {
display: flex;
align-items: center;
.course-cover {
width: 97px;
height: 55px;
display: inline-block;
border-radius: 4px;
margin-right: 8px;
}
.course-name {
font-size: 14px;
color: #666;
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
width: 140px;
}
}
.course-state {
* {
vertical-align: middle;
display: inline-block;
}
.status-point {
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 4px;
}
}
.footer {
position: fixed;
right: 0;
bottom: 0;
width: 720px;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 16px 24px;
background: #fff;
border-top: 1px solid #e8e8e8;
z-index: 9999;
.ant-btn {
margin-left: 8px;
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-03 17:05:32
* @LastEditors: yuananting
* @LastEditTime: 2021-08-18 17:25:30
* @Description: 新建培训任务-关联考试抽屉
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect, useRef } from 'react';
import { Drawer, Form, Input, Button, Tooltip, Switch, Radio, InputNumber, message } from 'antd';
import GraphicsEditor from '@/modules/course-manage/components/GraphicsEditor';
import moment from 'moment';
import './RelatedExamDrawer.less';
import SelectPaperModal from '@/modules/teach-tool/examination-manager/SelectPaperModal';
import User from '@/common/js/user';
import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
import Bus from '@/core/bus';
function RelatedExamDrawer(props) {
const [showPaperModal, setShowPaperModal] = useState(false);
const [paperInfo, setPaperInfo] = useState({});
const [answerAnalysis, setAnswerAnalysis] = useState('RIGHT_OR_WRONG'); // 答案与解析-默认仅显示对错
const [examDesc, setExamDesc] = useState(''); // 考试说明
const [examDuration, setExamDuration] = useState(null); // 考试时长
const [examName, setExamName] = useState(''); // 考试名称
const [needOptionDisorder, setNeedOptionDisorder] = useState('OPTION_SORT'); // 选项乱序-默认正常顺序
const [paperId, setPaperId] = useState(null); // 选择的试卷id
const [passRate, setPassRate] = useState(0); // 及格比例
const [passScore, setPassScore] = useState(60); // 及格分数
const [resultContent, setResultContent] = useState('PASS_AND_SCORE'); // 考试结果内容
const [resultShow, setResultShow] = useState('IMMEDIATELY'); // 考试结果查看-卷后立即显示考试结果
const [editorTextLength, setEditorTextLength] = useState(0); // 考试说明长度
const [check, setCheck] = useState(false);
const [examTotal, setExamTotal] = useState(0);
const [samePaper, setSamePaper] = useState(false);
const request = useRef(false);
useEffect(() => {
Bus.bind('editorLimit', (editorTextLength) => {
setEditorTextLength(editorTextLength);
});
}, []);
useEffect(() => {
setPaperId(paperInfo.paperId);
setPassRate(paperInfo.passRate);
setExamName(paperInfo.paperName);
}, [paperInfo.paperId]);
useEffect(() => {
setPassScore(Math.round(((paperInfo.totalScore || 0) * (passRate || 0)) / 100));
setExamTotal(paperInfo.singleChoiceCnt + paperInfo.multiChoiceCnt + paperInfo.judgeCnt + paperInfo.gapFillingCnt + paperInfo.indefiniteChoiceCnt || 0);
}, [paperInfo.paperId, passRate]);
function handleSave() {
if (request.current) {
return;
}
setCheck(true);
const params = {
paperId,
examName,
passRate,
examDuration: (examDuration || 0) * 60 * 1000,
examDesc,
needOptionDisorder,
resultShow,
resultContent,
answerAnalysis,
examEndTime: props.basicInfo.endTime || null,
examStartTime: props.basicInfo.startTime || new Date().valueOf(),
passScore,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
console.log('新建入参==============>', params);
if (
!paperId ||
samePaper ||
!examName ||
examName.length > 40 ||
!passRate ||
!examDuration ||
editorTextLength > 1000 ||
(props.basicInfo.timeType === 'VALIDITY' &&
props.basicInfo.startTime &&
props.basicInfo.endTime &&
props.basicInfo.startTime + examDuration * 60 * 1000 > props.basicInfo.endTime)
) {
return;
}
request.current = true;
setTimeout(() => {
request.current = false;
}, 2000);
TaskCenterService.createTrainingExam(params).then((res) => {
props.onSave(res.result);
});
}
return (
<Drawer title='添加考试' width={720} maskClosable={false} closable={true} onClose={props.onClose} visible={true} mask className='related-exam-drawer'>
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} layout='horizontal'>
<div className='module-title'>基本信息</div>
<Form.Item
label='选择试卷'
validateStatus={(check && !paperId) || samePaper ? 'error' : ''}
help={(check && !paperId && '请选择试卷') || (samePaper && '已存在相同试卷')}
required>
<Button
onClick={() => {
setShowPaperModal(true);
}}>
{paperInfo.paperId ? '重新选择' : '选择试卷'}
</Button>
{paperInfo.paperId && (
<div className='paper-title'>
<img src='https://image.xiaomaiketang.com/xm/pY5imEhjzw.png' alt='' /> {paperInfo.paperName}
</div>
)}
{paperInfo.paperId && (
<div className='paper-table'>
<div className='header'>
<div className='item'>单选题</div>
<div className='item'>多选题</div>
<div className='item'>判断题</div>
<div className='item'>填空题</div>
<div className='item long'>不定项选择题</div>
<div className='item'>合计</div>
</div>
<div className='body-list' style={{ borderBottom: '1px solid #e8e8e8' }}>
<div className='item'>{paperInfo.singleChoiceCnt || 0}</div>
<div className='item'>{paperInfo.multiChoiceCnt || 0}</div>
<div className='item'>{paperInfo.judgeCnt || 0}</div>
<div className='item'>{paperInfo.gapFillingCnt || 0}</div>
<div className='item long'>{paperInfo.indefiniteChoiceCnt || 0}</div>
<div className='item'>{examTotal}</div>
</div>
<div className='body-list'>
<div className='item'>{paperInfo.singleChoiceScore || 0}</div>
<div className='item'>{paperInfo.multiChoiceScore || 0}</div>
<div className='item'>{paperInfo.judgeScore || 0}</div>
<div className='item'>{paperInfo.gapFillingScore || 0}</div>
<div className='item long'>{paperInfo.indefiniteChoiceScore || 0}</div>
<div className='item'>{paperInfo.totalScore || 0}</div>
</div>
</div>
)}
</Form.Item>
<Form.Item
label='考试名称'
validateStatus={check && (!examName || examName.length > 40) ? 'error' : ''}
help={check && ((!examName && '请输入考试名称') || (examName.length > 40 && '考试名称最多40字'))}
required>
<Input
placeholder='请输入考试名称(40字以内)'
maxLength={40}
value={examName}
onChange={(e) => {
setExamName(e.target.value);
}}
style={{ width: 300 }}
/>
</Form.Item>
<Form.Item
label={
<div>
<span>及格线</span>
<Tooltip title='默认为选中试卷所设置的及格线,可修改'>
<span className='icon iconfont' style={{ color: '#BFBFBF', marginLeft: 4 }}>
&#xe61d;
</span>
</Tooltip>
</div>
}
style={{ marginTop: 24 }}
validateStatus={check && !passRate ? 'error' : ''}
help={check && !passRate && '请输入及格线'}
required>
<InputNumber
value={passRate}
min={0}
max={100}
onChange={(value) => {
setPassRate(parseInt(value));
}}
style={{ width: 100 }}
/>
<span style={{ marginLeft: 8 }}>%</span>
<span style={{ marginLeft: 16, color: '#999' }}>{` 总分(${paperInfo.totalScore || 0})*及格线(${passRate || 0}%)=及格分数(${passScore})`}</span>
</Form.Item>
<Form.Item
label='考试时长'
validateStatus={
check &&
(!examDuration ||
(props.basicInfo.timeType === 'VALIDITY' &&
props.basicInfo.startTime &&
props.basicInfo.endTime &&
props.basicInfo.startTime + examDuration * 60 * 1000 > props.basicInfo.endTime))
? 'error'
: ''
}
help={
check &&
((!examDuration && '请输入考试时长') ||
(props.basicInfo.timeType === 'VALIDITY' &&
props.basicInfo.startTime &&
props.basicInfo.endTime &&
props.basicInfo.startTime + examDuration * 60 * 1000 > props.basicInfo.endTime &&
'考试时长不得超过培训有效期时长'))
}
required>
<InputNumber
value={examDuration}
max={1440}
min={1}
onChange={(value) => {
setExamDuration(parseInt(value));
}}
style={{ width: 100 }}
/>
<span style={{ marginLeft: 8 }}>分钟</span>
<span style={{ marginLeft: 16, color: '#999' }}>{` 时长不能超过1440分钟(24小时)`}</span>
</Form.Item>
<Form.Item label='考试说明'>
<GraphicsEditor
maxLimit={1000}
isIntro={true}
detail={{
content: examDesc,
}}
onChange={(val) => {
setExamDesc(val);
}}
/>
</Form.Item>
<div className='module-title' style={{ marginTop: 16 }}>
考试设置
</div>
<Form.Item label='选项乱序' required>
<div style={{ display: 'flex', marginLeft: 4 }}>
<Switch
checked={needOptionDisorder == 'OPTION_RANDOM'}
onChange={(val) => {
setNeedOptionDisorder(val ? 'OPTION_RANDOM' : 'OPTION_SORT');
}}></Switch>
<div style={{ position: 'relative', left: 8, color: '#999' }}>
{needOptionDisorder == 'OPTION_RANDOM' ? '已开启,选项随机排序' : '已关闭,选项按设置顺序排序'}
</div>
</div>
</Form.Item>
<Form.Item label='考试结果查看' required>
<Radio.Group
onChange={(e) => {
setResultShow(e.target.value);
}}
value={resultShow}>
<Radio value={'IMMEDIATELY'}>交卷后立即显示考试结果</Radio>
<Radio value={'AFTER_EXAM_END'}>到达考试截止日期才显示结果</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label='考试结果内容' required>
<Radio.Group
onChange={(e) => {
setResultContent(e.target.value);
}}
value={resultContent}>
<Radio value={'PASS_AND_SCORE'}>显示考试分数和是否及格</Radio>
<Radio value={'ONLY_SCORE'}>仅显示考试分数</Radio>
<Radio value={'ONLY_PASS'}>仅显示是否及格</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label='答案与解析' required>
<Radio.Group
onChange={(e) => {
setAnswerAnalysis(e.target.value);
}}
value={answerAnalysis}>
<Radio value={'ANALYSE_AND_RIGHT_OR_WRONG'}>显示对错与解析</Radio>
<Radio value={'RIGHT_OR_WRONG'}>仅显示对错</Radio>
<Radio value={'CAN_NOT_CHECK'}>都不显示</Radio>
</Radio.Group>
</Form.Item>
</Form>
<div className='footer shrink-footer'>
<Button onClick={props.onClose}>取消</Button>
<Button type='primary' onClick={handleSave}>
确定
</Button>
</div>
{showPaperModal && (
<SelectPaperModal
onSelect={(info) => {
let newContentList = [];
props.stageList.map((item) => {
if (item.contentList && item.contentList.length > 0) {
item.contentList.map((childItem) => {
newContentList.push(childItem);
});
}
});
const existedPaperId = newContentList.filter((item) => {
return item.paperId === info.paperId;
});
if (existedPaperId.length > 0) {
setSamePaper(true);
} else {
setSamePaper(false);
}
setPaperInfo(info);
}}
paperInfo={paperInfo}
close={() => {
setShowPaperModal(false);
}}></SelectPaperModal>
)}
</Drawer>
);
}
export default RelatedExamDrawer;
.related-exam-drawer {
.module-title {
font-size: 16px;
color: #333333;
line-height: 22px;
margin-bottom: 24px;
}
.paper-title {
color: #333333;
line-height: 20px;
padding: 10px 12px;
margin-top: 12px;
margin-bottom: 8px;
img {
width: 20px;
height: 20px;
margin-right: 4px;
}
}
.paper-table {
border: 1px solid #e8e8e8;
width: 550px;
.header {
font-weight: 500;
.item {
padding: 10px 16px;
}
}
.body-list {
.item {
padding: 14px 16px;
}
}
.header,
.body-list {
display: flex;
color: #333333;
.item {
box-sizing: border-box;
width: 86px;
&.long {
width: 120px;
}
}
}
}
.footer {
position: fixed;
right: 0;
bottom: 0;
height: 50px;
width: 720px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 24px;
background: #fff;
border-top: 1px solid #e8e8e8;
z-index: 9999;
.ant-btn {
margin-left: 8px;
}
}
.ant-form-item {
margin-bottom: 24px !important;
.graphics-editor-container {
width: 550px;
height: 130px;
.editor-warning {
top: 130px !important;
}
}
}
}
/*
* @Author: yuananting
* @Date: 2021-07-30 16:33:58
* @LastEditors: yuananting
* @LastEditTime: 2021-08-17 18:19:34
* @Description: 任务中心-培训任务-新建-培训内容
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from 'react';
import { Form, Input, Collapse, Dropdown, Menu, Modal } from 'antd';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import arrayMove from 'array-move';
import './TrainContent.less';
import RelatedCourseDrawer from './RelatedCourseDrawer';
import RelatedExamDrawer from './RelatedExamDrawer';
import ExpiredCourseList from './ExpiredCourseList';
import ENUM from '../../enum';
import FormItem from 'antd/lib/form/FormItem';
const { Panel } = Collapse;
const { confirm } = Modal;
const SortableStageContainer = sortableContainer((props) => <div {...props}></div>);
const SortableStageItem = sortableElement((props) => <div {...props}>{props.stageitem}</div>);
const SortableContentContainer = sortableContainer((props) => <div {...props}></div>);
const SortableContentItem = sortableElement((props) => <div {...props}>{props.contentitem}</div>);
const DragHandle = sortableHandle(() => <span className='icon iconfont drag-btn'>&#xe61e;</span>);
const courseStateShow = {
UN_START: {
title: '待开播',
},
STARTING: {
title: '直播中',
},
FINISH: {
title: '回放',
},
EXPIRED: {
title: '未成功开课',
},
};
const SortConvert = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
class TrainContent extends Component {
constructor(props) {
super(props);
this.state = {
basicInfo: props.basicInfo,
stageList: props.stageList,
percentCompleteLive: props.percentCompleteLive,
percentCompleteVideo: props.percentCompleteVideo,
percentCompletePicture: props.percentCompletePicture,
showCourseDrawer: false,
showExamDrawer: false,
selectedStageIndex: 0,
type: window.getParameterByName('type'),
showStandardDetail: false, // 是否展开高级设置
};
}
setTrianTypeOption = (index) => {
return (
<Menu>
<Menu.Item key='course' onClick={() => this.setState({ showCourseDrawer: true, selectedStageIndex: index })}>
<img className='type-option-icon' src={ENUM.LearningContentIcon['COURSE']} />
<span className='type-option-text'>课程</span>
</Menu.Item>
<Menu.Item key='exam' onClick={() => this.setState({ showExamDrawer: true, selectedStageIndex: index })}>
<img className='type-option-icon' src={ENUM.LearningContentIcon['EXAM']} />
<span className='type-option-text'>考试</span>
</Menu.Item>
{/* <Menu.Item key='homework'>
<img className='type-option-icon' src={ENUM.LearningContentIcon['HOMEWORK']} />
<span className='type-option-text'>实操作业</span>
</Menu.Item> */}
</Menu>
);
};
onStageSortEnd = ({ oldIndex, newIndex }) => {
const { stageList } = this.state;
if (oldIndex !== newIndex) {
const _stageList = arrayMove([].concat(stageList), oldIndex, newIndex).filter((el) => !!el);
this.setState(
{
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
);
}
};
onContentSortEnd = ({ oldIndex, newIndex }, parentIndex) => {
const { stageList } = this.state;
const _stageList = [...stageList];
if (oldIndex !== newIndex) {
_stageList[parentIndex].contentList = arrayMove([].concat(stageList[parentIndex].contentList), oldIndex, newIndex).filter((el) => !!el);
this.setState(
{
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
);
}
};
handleRenameStageName = (value, item) => {
const { stageList } = this.state;
item.stageName = value;
item.check = true;
this.setState(
{
stageList,
},
() => {
this.props.onChange('stageList', stageList);
}
);
};
handleStageNameBlur = (e, item, currentIndex) => {
const { value } = e.target;
const { stageList } = this.state;
const sameStageName = stageList.filter((item, index) => {
return item.stageName === value && index !== currentIndex;
});
if (sameStageName.length > 0) {
return;
}
let input = /^[\s]*$/;
if (value && !input.test(value)) {
item.type = 'text';
this.setState(
{
stageList,
},
() => {
this.props.onChange('stageList', stageList);
}
);
}
};
handleValidatorStageName = (value, currentIndex) => {
const { stageList } = this.state;
const sameStageName = stageList.filter((item, index) => {
return item.stageName === value && index !== currentIndex;
});
let input = /^[\s]*$/;
if (input.test(value) || !value) {
return '请输入阶段名称';
} else if (sameStageName.length > 0) {
return '阶段名称已存在';
}
return false;
};
// 移除阶段
handleDeleteStage = (index) => {
return confirm({
title: '删除阶段',
content: '删除该阶段会连同课程、作业、考试一起删除,删除后不可恢复,是否仍要删除?',
icon: <span className='icon iconfont default-confirm-icon'>&#xe839; </span>,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: () => {
this.handleConfirmDeleteStage(index);
},
});
};
handleConfirmDeleteStage = (index) => {
const { stageList } = this.state;
const _stageList = [...stageList];
_stageList.splice(index, 1);
this.setState(
{
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
);
};
handleDeleteCourse = (parentIndex, index) => {
return confirm({
title: '删除学习内容',
content: '确定删除该学习内容吗?',
icon: <span className='icon iconfont default-confirm-icon'>&#xe839; </span>,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: () => {
this.handleConfirmDeleteCourse(parentIndex, index);
},
});
};
handleConfirmDeleteCourse = (parentIndex, index) => {
const { stageList } = this.state;
const _stageList = [...stageList];
const selectData = [..._stageList[parentIndex].contentList];
selectData.splice(index, 1);
_stageList[parentIndex].contentList = selectData;
this.setState(
{
stageList: _stageList,
},
() => {
this.props.onChange(_stageList);
}
);
};
renderStageInfo = (item, index) => {
const { startCheck } = this.props;
return (
<div className='sort-stage-item'>
<div className='item-info'>
<span className='info-number'>{SortConvert[index]}</span>
<Choose>
<When condition={item.type === 'input'}>
<Form>
<Form.Item
initialValue={item.stageName}
validateStatus={(item.check || startCheck) && this.handleValidatorStageName(item.stageName, index) ? 'error' : ''}
help={(item.check || startCheck) && this.handleValidatorStageName(item.stageName, index)}>
<Input
className='info-input'
style={{ width: 300 }}
placeholder='请输入阶段名称'
maxLength={20}
value={item.stageName}
onChange={(e) => {
this.handleRenameStageName(e.target.value, item);
}}
onBlur={(e) => {
this.handleStageNameBlur(e, item, index);
}}
/>
</Form.Item>
</Form>
</When>
<Otherwise>
<span className='info-text'>{item.stageName}</span>
</Otherwise>
</Choose>
</div>
<span className='item-operate'>
<span
className='operate__item'
onClick={(e) => {
if (item.type === 'input') return;
const { stageList } = this.state;
item.type = 'input';
item.check = true;
this.setState({ stageList });
e.stopPropagation();
}}>
<span className='icon iconfont'>&#xe6f5;</span>
<span className='text'>重命名</span>
</span>
<span
className='operate__item'
style={{ marginLeft: 16 }}
onClick={(e) => {
this.handleDeleteStage(index);
e.stopPropagation();
}}>
<span className='icon iconfont'>&#xe6f6;</span>
<span className='text'>删除</span>
</span>
</span>
<DragHandle />
</div>
);
};
renderStageItem = (item, index) => {
return (
<Collapse defaultActiveKey={this.state.type === 'add' && ['1']} ghost>
<Panel header={this.renderStageInfo(item, index)} key={item.sequence}>
<SortableContentContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={(item) => this.onContentSortEnd(item, index)}>
{item.contentList.map((contentItem, contentIndex) => (
<SortableContentItem
contentitem={this.renderContentItem(contentItem, contentIndex, index)}
index={contentIndex}
key={contentIndex}></SortableContentItem>
))}
</SortableContentContainer>
<Dropdown overlay={this.setTrianTypeOption(index)}>
<span className={`add-content-btn ${item.contentList.length === 20 && 'disabled'}`}>+ 添加学习内容</span>
</Dropdown>
</Panel>
</Collapse>
);
};
renderContentItem = (record, index, parentIndex) => {
const { courseState, contentName, contentType, courseType, courseChapterNum } = record;
return (
<div className='sort-content-item'>
<div className='content-info'>
<img className='type-option-icon' src={ENUM.LearningContentIcon[courseType || contentType]} />
<span className='content-name'>
{parentIndex + 1}.{index + 1} {contentName}
</span>
{courseState === 'EXPIRED' && <span className='icon iconfont tip'>&#xe834;</span>}
{courseType === 'LIVE' && <span className='extra-info'>{courseStateShow[courseState].title}</span>}
{courseType === 'VOICE' && <span className='extra-info'>(共{courseChapterNum || 1}小节)</span>}
</div>
<div className='content-operate'>
<span
className='operate__item'
onClick={() => {
this.handleDeleteCourse(parentIndex, index);
}}>
<span className='icon iconfont'>&#xe6f6;</span>
<span className='text'>删除</span>
</span>
</div>
<DragHandle />
</div>
);
};
// 添加阶段
addStage = () => {
const { stageList } = this.state;
const stageObj = {
stageName: '',
type: 'input',
contentList: [],
check: false,
};
const _stageList = [...stageList, stageObj];
this.setState(
{
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
);
};
onCloseCourseDrawer = () => {
this.setState({
showCourseDrawer: false,
});
};
onCloseExamDrawer = () => {
this.setState({
showExamDrawer: false,
});
};
confirmSelectCourse = (selectList) => {
const { selectedStageIndex } = this.state;
const { stageList } = this.state;
const _stageList = [...stageList];
const selectData = [..._stageList[selectedStageIndex].contentList];
let _selectList = [];
selectList.forEach((item) => {
_selectList.push({
contentId: item.courseId,
contentName: item.courseName,
contentType: 'COURSE',
courseType: item.courseType,
courseState: item.courseState,
});
});
const _selectData = [...selectData, ..._selectList];
_stageList[selectedStageIndex].contentList = _selectData;
this.setState(
{
showCourseDrawer: false,
stageList: _stageList,
},
() => {
console.log('_stageList=============>', _stageList);
this.props.onChange('stageList', _stageList);
}
);
};
confirmCreateExam = (examInfo) => {
const { selectedStageIndex } = this.state;
const { stageList } = this.state;
const _stageList = [...stageList];
const selectData = [..._stageList[selectedStageIndex].contentList];
const _examInfo = {
contentId: examInfo.examId,
contentName: examInfo.examName,
contentType: 'EXAM',
paperId: examInfo.paperId,
};
selectData.push(_examInfo);
_stageList[selectedStageIndex].contentList = selectData;
this.setState(
{
showExamDrawer: false,
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
);
};
changePercentComplete = (e, field) => {
let _percentComplete = 0;
const value = e.target.value.replace(/\D/g, '');
if (value > 100) {
_percentComplete = 100;
} else {
if (value < 0) {
_percentComplete = 0;
} else {
_percentComplete = value;
}
}
this.setState(
{
[field]: _percentComplete,
},
() => {
this.props.onChange(field, _percentComplete);
}
);
};
render() {
const {
stageList,
showCourseDrawer,
showExamDrawer,
showStandardDetail,
basicInfo,
selectedStageIndex,
percentCompleteLive,
percentCompleteVideo,
percentCompletePicture,
type,
} = this.state;
const { startCheck } = this.props;
const taskId = window.getParameterByName('taskId');
return (
<div className='train-content-page'>
<div className='train-content__warp'>
<SortableStageContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onStageSortEnd}>
{stageList.map((item, index) => (
<SortableStageItem stageitem={this.renderStageItem(item, index)} index={index} key={index}></SortableStageItem>
))}
</SortableStageContainer>
{stageList.length < 10 && (
<div className='add-stage-btn' onClick={() => this.addStage()}>
+ 添加阶段
</div>
)}
{showCourseDrawer && (
<RelatedCourseDrawer
data={stageList}
selectedStageIndex={selectedStageIndex}
onClose={this.onCloseCourseDrawer}
onSelect={this.confirmSelectCourse}
/>
)}
{showExamDrawer && <RelatedExamDrawer basicInfo={basicInfo} stageList={stageList} onClose={this.onCloseExamDrawer} onSave={this.confirmCreateExam} />}
</div>
{type === 'edit' && <ExpiredCourseList taskId={taskId} />}
<div className='finish-standard__warp'>
<div
className='module-title'
onClick={() => {
this.setState({ showStandardDetail: !showStandardDetail });
}}>
高级设置
<span className={`icon iconfont ${showStandardDetail && 'rotate-arrow'}`}>&#xe614;</span>
</div>
{showStandardDetail && (
<div className='detail-container'>
<div className='title-text'>完成标准:</div>
<Form className='detail-box'>
<div className='item-info'>
<span>
<img src={ENUM.LearningContentIcon['LIVE']} />
</span>
<span>
直播课单个课程,学员学习进度达到
<FormItem
validateStatus={startCheck && percentCompleteLive === '' ? 'error' : ''}
help={startCheck && percentCompleteLive === '' && '请输入完成标准'}>
<Input
value={percentCompleteLive}
onChange={(e) => {
this.changePercentComplete(e, 'percentCompleteLive');
}}
className='input-box'
/>
</FormItem>
%,即视为"已完成"学习
</span>
</div>
<div className='item-info'>
<img src={ENUM.LearningContentIcon['VOICE']} />
<span>
线上课单个课节,学员学习进度达到
<FormItem
validateStatus={startCheck && percentCompleteVideo === '' ? 'error' : ''}
help={startCheck && percentCompleteVideo === '' && '请输入完成标准'}>
<Input
value={percentCompleteVideo}
onChange={(e) => {
this.changePercentComplete(e, 'percentCompleteVideo');
}}
className='input-box'
/>
</FormItem>
%,即课节视为"已完成"学习
</span>
</div>
<div className='item-info'>
<img src={ENUM.LearningContentIcon['PICTURE']} />
<span>
图文课单个课程,学员学习进度达到
<FormItem
validateStatus={startCheck && percentCompletePicture === '' ? 'error' : ''}
help={startCheck && percentCompletePicture === '' && '请输入完成标准'}>
<Input
value={percentCompletePicture}
onChange={(e) => {
this.changePercentComplete(e, 'percentCompletePicture');
}}
className='input-box'
/>
</FormItem>
%,即视为"已完成"学习
</span>
</div>
</Form>
</div>
)}
</div>
</div>
);
}
}
export default TrainContent;
.train-content-page {
.train-content__warp {
.ant-collapse-header {
padding: 15px 16px !important;
background-color: #f7f8f9;
}
.add-stage-btn {
color: #2966ff;
height: 52px;
background: #f7f8f9;
padding: 16px;
cursor: pointer;
}
}
.expired-info__wrap {
margin-top: 24px;
.module-title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
}
}
.finish-standard__warp {
margin-top: 24px;
margin-bottom: 60px;
.module-title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
cursor: pointer;
.icon {
font-size: 12px;
margin-left: 8px;
color: #5e606a;
display: inline-block;
vertical-align: middle;
}
.rotate-arrow {
transform: (rotate(180deg));
}
}
.detail-container {
margin-top: 24px;
display: flex;
.title-text {
color: #333333;
line-height: 20px;
}
.detail-box {
margin-top: -4px;
.item-info {
color: #333333;
vertical-align: middle;
margin-bottom: 20px;
.ant-form-item {
display: inline-block;
margin-bottom: 0 !important;
vertical-align: baseline;
}
img {
width: 20px;
height: 20px;
margin-right: 8px;
}
.input-box {
width: 60px;
height: 32px;
border-radius: 2px;
border: 1px solid #e8e8e8;
margin: 0 4px;
}
}
}
}
}
}
.sort-stage-item {
width: calc(100% - 24px);
display: inline-flex;
align-items: center;
line-height: 20px;
.item-info {
color: #333333;
}
.item-operate {
display: none;
margin-left: 30px;
.operate__item {
cursor: pointer;
.icon {
color: #bfbfbf;
font-size: 14px;
}
.text {
color: #666666;
margin-left: 8px;
}
}
}
&:hover {
.item-operate {
display: block;
}
}
.ant-form {
display: inline-block;
.ant-form-item {
margin-bottom: 0 !important;
}
}
}
.sort-content-item {
display: flex;
padding: 15px 16px !important;
margin-left: 40px;
justify-content: space-between;
align-items: center;
border-bottom: 1px dotted #e8e8e8;
vertical-align: middle;
* {
vertical-align: middle;
height: 20px;
}
&:hover {
.content-operate {
display: block;
}
}
.content-info {
.content-name {
color: #333333;
font-size: 14px;
margin-left: 12px;
}
.extra-info {
color: #999999;
margin-left: 12px;
}
.tip {
font-size: 14px;
color: #ff4f4f;
margin-right: 2px;
}
}
.content-operate {
display: none;
margin-left: 26px;
.operate__item {
cursor: pointer;
color: #666666;
.icon {
font-size: 14px;
color: #bfbfbf;
}
.text {
margin-left: 8px;
}
}
}
}
.type-option-icon {
width: 20px;
height: 20px;
}
.type-option-text {
margin-left: 12px;
}
.drag-btn {
margin-left: auto;
color: #cccccc;
cursor: pointer;
}
.add-content-btn {
color: #2966ff;
height: 52px;
border-radius: 2px;
padding: 16px;
cursor: pointer;
display: inline-block;
&.disabled {
color: #ccc;
cursor: not-allowed;
pointer-events: none;
}
}
.ant-collapse-content {
padding-left: 24px !important;
> .ant-collapse-content-box {
padding: 0 !important;
}
}
/*
* @Author: 吴文洁
* @Date: 2020-07-14 15:41:30
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-23 13:45:16
* @Description: 大班直播、互动班课列表的筛选组件
*/
import React, { useState, useEffect } from 'react';
import { Row, Input, Select, Tooltip } from 'antd';
import RangePicker from '@/modules/common/DateRangePicker';
import moment from 'moment';
import StoreService from '@/domains/store-domain/storeService';
import './TrainFilter.less';
const { Search } = Input;
const DEFAULT_QUERY = {
// 头部筛选默认值
taskName: null,
createId: null, // 创建人
startTime: null,
endTime: null,
};
const DEFAULT_CREATOR_QUERY = {
// 创建人列表筛选默认值
size: 10,
current: 1,
nickName: null, // 搜索值
};
function TrainFilter(props) {
const [query, setQuery] = useState(DEFAULT_QUERY);
const [hasNext, setHasNext] = useState(false);
const [creatorQuery, setCreatorQuery] = useState(DEFAULT_CREATOR_QUERY);
const [creatorList, setCreatorList] = useState([]);
useEffect(() => {
getCreatorList();
}, []);
// 获取创建人列表
function getCreatorList(current = 1) {
const _query = {
...creatorQuery,
current,
};
StoreService.getStoreUserBasicPage(_query).then((res) => {
const { result = {} } = res;
const { records = [], hasNext } = result;
const list = current > 1 ? creatorList.concat(records) : records;
setHasNext(hasNext);
setCreatorList(list);
});
}
// 滑动加载更多创建人列表(讲师)
function handleScrollCreatorList(e) {
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
const _creatorQuery = { ...creatorQuery };
_creatorQuery.current = creatorQuery.current + 1;
setCreatorQuery(_creatorQuery);
getCreatorList(creatorQuery.current + 1);
}
}
// 改变搜索条件
function handleChangeQuery(field, value) {
const _query = _.clone(query);
_query.current = 1;
if (field === 'createdDate') {
_query.startTime = value[0]?.startOf('day').valueOf();
_query.endTime = value[1]?.endOf('day').valueOf();
} else {
_query[field] = value;
}
setQuery(_query);
if (field === 'taskName') return;
props.onChange(_query);
}
// 重置搜索条件
function handleReset() {
setQuery(DEFAULT_QUERY);
props.onChange(DEFAULT_QUERY);
}
return (
<div className='train-filter-page'>
<Row>
<div className='search-condition'>
<div className='search-condition__item'>
<span>培训任务:</span>
<Search
value={query.taskName}
placeholder='搜索培训任务名称'
onChange={(e) => {
handleChangeQuery('taskName', e.target.value.trim());
}}
onSearch={() => {
props.onChange(query);
}}
style={{ width: 'calc(100% - 70px)' }}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
</div>
<div className='search-condition__item'>
<span>创建人:</span>
<Select
placeholder='请选择创建人'
style={{ width: 'calc(100% - 70px)' }}
showSearch
allowClear
filterOption={(input, option) => option}
suffixIcon={
<span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
&#xe835;
</span>
}
onPopupScroll={handleScrollCreatorList}
value={query.createId}
onChange={(value) => {
handleChangeQuery('createId', value);
}}
onSearch={(value) => {
creatorQuery.nickName = value;
setCreatorQuery(creatorQuery);
getCreatorList();
}}
onClear={() => {
setCreatorQuery(DEFAULT_CREATOR_QUERY);
getCreatorList();
}}>
{_.map(creatorList, (item, index) => {
return (
<Select.Option value={item.id} key={item.id}>
{item.nickName}
</Select.Option>
);
})}
</Select>
</div>
<div className='search-condition__item'>
<span className='search-date'>创建日期:</span>
<RangePicker
id='train_date_picker'
allowClear={false}
value={query.startTime ? [moment(query.startTime), moment(query.endTime)] : null}
format={'YYYY-MM-DD'}
onChange={(value) => {
handleChangeQuery('createdDate', value);
}}
style={{ width: 'calc(100% - 70px)' }}
/>
</div>
</div>
<div className='reset-fold-area'>
<Tooltip title='清空筛选'>
<span className='resetBtn iconfont icon' onClick={handleReset}>
&#xe61b;{' '}
</span>
</Tooltip>
</div>
</Row>
</div>
);
}
export default TrainFilter;
.train-filter-page {
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
line-height: 32px;
}
}
.reset-fold-area {
position: absolute;
right: 12px;
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
}
}
/*
* @Author: yuananting
* @Date: 2021-07-28 14:56:52
* @LastEditors: yuananting
* @LastEditTime: 2021-08-18 12:37:50
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import { Tooltip, Checkbox, Dropdown, Radio, Button, Space, Modal, message } from 'antd';
import './TrainList.less';
import { XMTable, PageControl } from '@/components';
import User from '@/common/js/user';
import ENUM from '../../enum';
import TaskCenterService from '@/domains/task-center-domain/TaskCenterService';
import ShareTrainTaskModal from '../modal/ShareTrainTaskModal';
import ChooseAssignorModal from '../modal/ChooseAssignorModal';
import { LIVE_SHARE } from '@/domains/course-domain/constants';
import Bus from '@/core/bus';
function TrainList(props) {
const {
query: { issueState, myAssist, current, size },
totalCount,
match,
} = props;
const [shareTrainTaskModal, setShareTrainTaskModal] = useState(null);
const [chooseAssignorModal, setChooseAssignorModal] = useState(null);
// 发布或取消发布培训任务
function updateIssueStateTrain(taskId, state) {
Modal.confirm({
title: '提示',
content:
state === 'YES' ? '发布后,被指派学员将任务列表中看到该任务,确定要发布?' : '取消发布后,任务对学员暂不可见,可能会影响正在学习学员,确定要取消?',
okText: '确定',
cancelText: '取消',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
TaskCenterService.updateIssueStateTraining({
taskId,
issueState: state,
}).then((res) => {
message.success(state === 'YES' ? '发布成功' : '取消发布成功');
Bus.trigger('getTrainingTaskPage', { issueState, myAssist });
Bus.trigger('getStoreTaskNum');
});
},
});
}
// 删除培训任务
function deleteTrainTask(taskId) {
Modal.confirm({
title: '你确定要删除吗?',
content: '删除后,此培训任务的用户将无法继续学习,所有学习数据将同步删除不可恢复',
okText: '确定',
cancelText: '取消',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
TaskCenterService.deleteTrainingTask({
taskId,
}).then((res) => {
message.success('删除成功');
Bus.trigger('getTrainingTaskPage', { issueState, myAssist });
Bus.trigger('getStoreTaskNum');
});
},
});
}
// 编辑培训任务-跳转新建/编辑页
function editTrainingTask(item) {
window.RCHistory.push({
pathname: `/create-train-task?type=edit&taskId=${item.taskId}&taskState=${item.taskState}&timeType=${item.timeType}`,
});
}
// 显示指派弹窗
function handleShowAssignModal(item) {
let _assignList = [];
if (item.assignList?.length > 0) {
_assignList = item.assignList.map((childItem) => {
childItem.checkedId = childItem.assignId;
childItem.checkedName = childItem.assignName;
childItem.checkedType = childItem.assignType;
return childItem;
});
}
const chooseAssignorModal = (
<ChooseAssignorModal
currentAssignorList={_assignList}
visible={true}
onClose={() => {
setChooseAssignorModal(null);
}}
onConfirm={(data) => {
confirmUpdatedAssignor(data, item.taskId);
}}
/>
);
setChooseAssignorModal(chooseAssignorModal);
}
// 显示分享弹窗
function handleShowShareModal(item) {
const htmlUrl = `${LIVE_SHARE}training_task_detail/${item.taskId}?id=${User.getStoreId()}&storeUserId=${User.getStoreUserId()}`;
const longUrl = htmlUrl;
const shareData = { ...item, longUrl };
const shareTrainTaskModal = (
<ShareTrainTaskModal
data={shareData}
type='liveClass'
close={() => {
setShareTrainTaskModal(null);
}}
/>
);
setShareTrainTaskModal(shareTrainTaskModal);
}
// 确定指派人员
function confirmUpdatedAssignor(data, id) {
const assignList = data.map((item) => {
return {
assignType: item.checkedType,
assignId: item.checkedId,
};
});
const params = {
assignList,
id,
};
if (assignList.length === 0) {
return message.warning('指派列表不能为空');
}
TaskCenterService.updateTrainingTaskAssign(params).then((res) => {
message.success('指派成功');
Bus.trigger('getTrainingTaskPage', { issueState, myAssist });
setChooseAssignorModal(null);
});
}
function renderMoreOperate(item) {
const isUnableIssue = item.issueState === 'YES'; // 已发布
const isUnableAbleEdit = item.issueState === 'YES' || item.taskState === 'FINISH'; // 已发布或已结束
const isUnableShare = item.issueState === 'NO' || item.taskState === 'FINISH'; //未发布或已结束
return (
<div className='train-list-more-menu'>
{!isUnableIssue && (
<div className='operate__item' onClick={() => updateIssueStateTrain(item.taskId, 'YES')}>
发布
</div>
)}
{isUnableIssue && (
<div className='operate__item' onClick={() => updateIssueStateTrain(item.taskId, 'NO')}>
取消发布
</div>
)}
<div
className={`operate__item ${isUnableAbleEdit && 'disabled'} `}
onClick={() => {
editTrainingTask(item);
}}>
编辑
</div>
<div className={`operate__item ${isUnableShare && 'disabled'} `} onClick={() => handleShowShareModal(item)}>
分享
</div>
{/* <div className='operate__item'>审批作业</div> */}
<div
className='operate__item'
onClick={() => {
deleteTrainTask(item.taskId);
}}>
删除
</div>
</div>
);
}
function parseColumns() {
const columns = [
{
title: '培训任务',
key: 'taskName',
dataIndex: 'taskName',
width: '18%',
fixed: 'left',
render: (val, record) => {
return (
<div className='train-task-name'>
<img className='train-cover' src={record.coverUrl || 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png'} alt='' />
<Choose>
<When condition={record.taskName?.length > 25}>
<Tooltip title={record.taskName}>
<div className='train-name'>{val}</div>
</Tooltip>
</When>
<Otherwise>
<div className='train-name'>{val}</div>
</Otherwise>
</Choose>
</div>
);
},
},
{
title: '任务状态',
width: '12%',
key: 'taskState',
dataIndex: 'taskState',
render: (val) => {
return (
<div className='task-state'>
<span className='status-point' style={{ backgroundColor: ENUM.trainStatus[val || 'UN_START'].color }}></span>
<span>{ENUM.trainStatus[val || 'UN_START'].text}</span>
</div>
);
},
},
{
title: '任务数',
width: '8%',
key: 'contentNum',
dataIndex: 'contentNum',
render: (val, record) => {
return <span>{val}</span>;
},
},
{
title: '学习人数',
width: '12%',
key: 'cultureCustomerNum',
dataIndex: 'cultureCustomerNum',
render: (val, record) => {
return (
<Tooltip
title={
<div>
<div>未完成:{record.startingCustomerNum}</div>
<div>已完成:{record.finishCustomerNum}</div>
<div>已逾期:{record.overdueCustomerNum}</div>
</div>
}>
<span
style={{ color: '#2966FF', cursor: 'pointer' }}
onClick={() => {
props.history.push(`${match.path}/data/${record.taskId}`);
}}>
{val}
</span>
</Tooltip>
);
},
},
{
title: (
<span>
完成率
<Tooltip title='完成培训任务的人数/学习总人数'>
<i
className='icon iconfont'
style={{
marginLeft: '4px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: 'normal',
}}>
&#xe61d;
</i>
</Tooltip>
</span>
),
width: '11.5%',
key: 'finishPercent',
dataIndex: 'finishPercent',
render: (val) => {
return <span>{val}%</span>;
},
},
{
title: '培训时间',
width: '17%',
key: 'trainTime',
dataIndex: 'trainTime',
render: (val, record) => {
if (record.timeType === 'FOREVER') {
return <span>不限时</span>;
}
return (
<span>
{window.formatDate('YYYY-MM-DD H:i', record.startTime)} ~ {window.formatDate('YYYY-MM-DD H:i', record.endTime)}
</span>
);
},
},
{
title: '创建人',
key: 'createName',
dataIndex: 'createName',
width: '12%',
render: (val) => {
return <span>{val}</span>;
},
},
{
title: '创建时间',
width: '14%',
key: 'created',
dataIndex: 'created',
sorter: true,
render: (val) => {
return <span style={{ whiteSpace: 'nowrap' }}>{window.formatDate('YYYY-MM-DD H:i', val)}</span>;
},
},
{
title: '更新时间',
width: '14%',
key: 'updated',
dataIndex: 'updated',
sorter: true,
render: (val) => {
return <span style={{ whiteSpace: 'nowrap' }}>{window.formatDate('YYYY-MM-DD H:i', val)}</span>;
},
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
fixed: 'right',
width: '18%',
render: (val, record) => {
return (
<div className='operate'>
<div
className='operate__item'
onClick={() => {
props.history.push(`${match.path}/data/${record.taskId}`);
}}>
数据
</div>
<span className='split'> | </span>
<div
className={`operate__item ${record.taskState === 'FINISH' && 'disabled'} `}
onClick={() => {
handleShowAssignModal(record);
}}>
指派
</div>
<span className='split'> | </span>
<Dropdown overlay={renderMoreOperate(record)}>
<span className='more-operate'>
<span className='more-text'>更多</span>
<span className='iconfont icon' style={{ color: '#2966FF' }}>
&#xe824;
</span>
</span>
</Dropdown>
</div>
);
},
},
];
return columns;
}
// 改变搜索条件(状态 协同与否)
function handleChangeQuery(field, value) {
const _query = {
...props.query,
[field]: value,
current: 1,
};
props.onChange(_query);
}
function handleChangeTable(pagination, filters, sorter) {
const { columnKey, order } = sorter;
const { query } = props;
let _columnKey;
let _order;
if (columnKey === 'created' && order === 'ascend') {
// 按创建时间升序排序
_columnKey = 'CREATED';
_order = 'SORT_ASC';
}
if (columnKey === 'created' && order === 'descend') {
// 按创建时间降序排序
_columnKey = 'CREATED';
_order = 'SORT_DESC';
}
if (columnKey === 'updated' && order === 'ascend') {
// 按更新时间升序排序
_columnKey = 'UPDATED';
_order = 'SORT_ASC';
}
if (columnKey === 'updated' && order === 'descend') {
// 按更新时间降序排序
_columnKey = 'UPDATED';
_order = 'SORT_DESC';
}
const _query = {
...query,
sortMap: {
UPDATED: 'SORT_DESC',
},
};
_query.sortMap[_columnKey] = _order;
props.onChange(_query);
}
function handleCreateTask() {
window.RCHistory.push({
pathname: '/create-train-task?type=add',
});
}
return (
<div className='train-list-page'>
<div className='header-line'>
{(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<Button type='primary' className='mr12' onClick={handleCreateTask}>
新建培训任务
</Button>
)}
<Space size={16}>
<Radio.Group
size='middle'
value={issueState}
onChange={(e) => {
handleChangeQuery('issueState', e.target.value);
}}>
<Radio.Button value='ALL'>全部({props.storeTaskNum.allNum})</Radio.Button>
<Radio.Button value='YES'>已发布({props.storeTaskNum.issueNum})</Radio.Button>
<Radio.Button value='NO'>未发布({props.storeTaskNum.notIssueNum})</Radio.Button>
</Radio.Group>
<Checkbox style={{ lineHeight: '32px' }} value={myAssist} onChange={(e) => handleChangeQuery('myAssist', e.target.checked)}>
只看我协同的 ({props.storeTaskNum.myAssistNum})
</Checkbox>
</Space>
</div>
<div className='list-content'>
<XMTable
rowKey={(record) => record.id}
showSorterTooltip={false}
dataSource={props.trainListData}
columns={parseColumns()}
pagination={false}
onChange={handleChangeTable}
bordered
size='middle'
scroll={{ x: 1400 }}
className='train-list-table'
renderEmpty={{
description: <span style={{ display: 'block', paddingBottom: 24 }}>暂无数据</span>,
}}
/>
<div className='box-footer'>
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = {
...props.query,
current: page + 1,
};
props.onChange(_query);
}}
showSizeChanger={false}
/>
</div>
</div>
{shareTrainTaskModal}
{chooseAssignorModal}
</div>
);
}
export default withRouter(TrainList);
.train-list-page {
.header-line {
display: flex;
justify-content: space-between;
margin-top: 16px;
margin-bottom: 12px;
.ant-radio-button-wrapper {
color: #9d9d9d;
&:first-child {
border-radius: 4px 0 0 4px;
}
&:last-child {
border-radius: 0 4px 4px 0;
}
}
.ant-radio-button-wrapper-checked {
color: #2966ff;
}
}
.train-list-table {
.train-task-name {
display: flex;
align-items: center;
cursor: pointer;
.train-cover {
width: 106px;
height: 60px;
border-radius: 2px;
margin-right: 8px;
flex-shrink: 0;
}
.train-name {
width: 188px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
height: 40px;
}
}
.task-state {
* {
vertical-align: middle;
display: inline-block;
}
.status-point {
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 4px;
}
}
.operate {
display: flex;
&__item {
color: #2966ff;
cursor: pointer;
}
.split {
margin: 0 8px;
color: #bfbfbf;
}
.more-operate {
cursor: pointer;
.more-text {
color: #2966ff;
cursor: pointer;
}
}
.disabled {
color: #ccc !important;
cursor: not-allowed !important;
pointer-events: none !important;
}
}
.tip {
cursor: pointer;
color: '#bfbfbf';
margin-left: 5px;
font-weight: normal;
}
thead.ant-table-thead {
tr {
th {
padding: 10px 16px;
}
}
}
tbody {
tr {
td.ant-table-cell {
padding: 20px 16px;
color: #333;
}
&:nth-child(even) {
background: transparent;
td {
background: #fff;
}
}
&:nth-child(odd) {
background: #fafafa;
td {
background: #fafafa;
}
}
&:hover {
td {
background: #f3f6fa;
}
}
}
}
}
}
.train-list-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;
}
}
.disabled {
color: #ccc !important;
cursor: not-allowed !important;
pointer-events: none !important;
}
}
/*
* @Author: yuananting
* @Date: 2021-08-05 17:09:36
* @LastEditors: yuananting
* @LastEditTime: 2021-08-18 16:23:22
* @Description: 新建培训任务-选择指派学员
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect, useRef } from 'react';
import { Modal, Tree, Tooltip, Tabs, Input, Checkbox } from 'antd';
import User from '@/common/js/user';
import { DepType } from '@/domains/store-domain/constants';
import StoreService from '@/domains/store-domain/storeService';
import WWOpenDataCom from '@/components/WWOpenDataCom';
import _ from 'underscore';
import './ChooseAssignorModal.less';
import $ from 'jquery';
import LottieIcon from '@/components/LottieIcon';
const { TabPane } = Tabs;
const { DirectoryTree } = Tree;
const { Search } = Input;
function ChooseAssignorModal(props) {
const AssignTypeEnum = {
departMentTab: 'SECTION',
postGrouptab: 'POST',
customGroupTab: 'CUSTOM',
};
const DeptTypeEnum = {
departMentTab: ['部门'],
postGrouptab: ['岗位', '岗位组'],
customGroupTab: ['分组', '分组集合'],
};
const [dropDownVisible, setDropDownVisible] = useState(false);
const [structureData, setStructureData] = useState([]);
const [activeKey, setActiveKey] = useState('departMentTab');
const [checkedAssignorList, setCheckedAssignorList] = useState(props.currentAssignorList || []); // 勾选的指派学员
const [checkedAssignorKeys, setCheckedAssignorKeys] = useState(handleCheckedKeys(props.currentAssignorList, props.currentAssignorList, []));
const [queryName, setQueryName] = useState(''); // 搜索框内的值
const [departmentUserVOList, setDepartmentUserVOList] = useState([]);
const [departmentVOList, setDepartmentVOList] = useState([]);
const [subLevelDepartmentVOList, setSubLevelDepartmentVOList] = useState([]);
const queryNameRef = useRef(null);
const timer = useRef(null);
useEffect(() => {
queryNameRef.current = queryName;
clearTimeout(timer.current);
timer.current = setTimeout(() => {
if (!!queryName) return getCompleteOptionData(queryNameRef.current);
}, 500);
}, [queryName]);
useEffect(() => {
setQueryName(''); // 切换tab时搜索置空
setDepartmentUserVOList([]);
setDepartmentVOList([]);
setSubLevelDepartmentVOList([]);
setStructureData([]);
getStructureData();
}, [activeKey]);
useEffect(() => {
documentClick();
}, []);
function getStructureData() {
const params = {
depType: DepType[activeKey],
enterpriseId: User.getEnterpriseId(),
source: 0, // 0代表来自企培
storeId: User.getStoreId(),
userId: User.getUserId(),
whetherCount: false,
distinct: false,
queryType: 'CUSTOMER',
};
StoreService.getDepartmentUserNotPage(params).then((res) => {
const { result = [] } = res;
const structureData = handleStructureData(result.departmentVOList);
const checkedKeys = handleCheckedKeys(structureData, checkedAssignorList, []);
setStructureData(structureData);
setCheckedAssignorKeys(checkedKeys);
});
}
function handleCheckedKeys(dataArray, checkedAssignorList, checkedKeys) {
dataArray.map((item, index) => {
if (checkedAssignorList.find((checkedItem) => checkedItem.checkedId === item.checkedId)) {
checkedKeys.push(item.id);
}
if (item.children?.length > 0) {
handleCheckedKeys(item.children, checkedAssignorList, checkedKeys);
}
});
return checkedKeys;
}
function handleStructureData(dataArray) {
const _dataArray = dataArray.map((item, index) => {
item.title = '';
item.checkedId = item.storeCustomerId || item.id;
item.checkedType = item.storeCustomerId ? 'CUSTOMER' : AssignTypeEnum[activeKey];
item.checkedName = item.storeCustomerId ? item.userName : item.name;
item.key = item.id;
item.children = [];
if (activeKey !== 'departMentTab' && item.depLevel === 0) {
item.disableCheckbox = true;
}
if (item.departmentUserVOList) {
item.children = item.departmentUserVOList;
}
if (item.sonDepartmentVOList) {
item.children = [...item.children, ...item.sonDepartmentVOList];
}
if (item.children.length > 0) {
handleStructureData(item.children);
} else {
delete item.children;
}
return item;
});
return _dataArray;
}
function onCheckAssignor(key, e) {
const { node, checked } = e;
let _checkedAssignorList = [...checkedAssignorList];
if (checked) {
_checkedAssignorList.push(node);
} else {
_checkedAssignorList = _checkedAssignorList.filter((item) => {
return item.checkedId !== node.checkedId;
});
}
const _checkedAssignorKeys = handleCheckedKeys(structureData, _checkedAssignorList, []);
setCheckedAssignorList(_checkedAssignorList);
setCheckedAssignorKeys(_checkedAssignorKeys);
}
function removeCheckedAssignor(item) {
const _checkedAssignorList = checkedAssignorList.filter((childItem) => childItem.checkedId !== item.checkedId);
const _checkedAssignorKeys = _checkedAssignorList.map((item) => item.id);
setCheckedAssignorKeys(_checkedAssignorKeys);
setCheckedAssignorList(_checkedAssignorList);
}
function clearCheckedAssignor() {
setCheckedAssignorKeys([]);
setCheckedAssignorList([]);
}
function getCompleteOptionData(value) {
const params = {
depType: DepType[activeKey],
queryName: value,
enterpriseId: User.getEnterpriseId(),
source: 0, //0代表来自企培
storeId: User.getStoreId(),
userId: User.getUserId(),
whetherCount: false,
distinct: false,
queryType: 'CUSTOMER',
};
StoreService.getDepartmentUser(params).then((res) => {
const { result = {} } = res;
const { departmentUserVOList = [], departmentVOList = [], subLevelDepartmentVOList = [] } = result;
const _departmentUserVOList = departmentUserVOList.map((item) => {
item.checkedName = item.userName;
item.checkedId = item.storeCustomerId;
item.checked = checkedAssignorList.find((checkedItem) => checkedItem.checkedId === item.checkedId);
item.checkedType = 'CUSTOMER';
return item;
});
const _departmentVOList = departmentVOList.map((item) => {
item.disabled = activeKey !== 'departMentTab';
item.checkedName = item.name;
item.checkedId = item.id;
item.checked = checkedAssignorList.find((checkedItem) => checkedItem.checkedId === item.checkedId);
item.checkedType = AssignTypeEnum[activeKey];
return item;
});
const _subLevelDepartmentVOList = subLevelDepartmentVOList.map((item) => {
item.checkedName = item.name;
item.checkedId = item.id;
item.checked = checkedAssignorList.find((checkedItem) => checkedItem.checkedId === item.checkedId);
item.checkedType = AssignTypeEnum[activeKey];
return item;
});
setDepartmentUserVOList(_departmentUserVOList);
setDepartmentVOList(_departmentVOList);
setSubLevelDepartmentVOList(_subLevelDepartmentVOList);
});
}
// 搜索空状态渲染;
function renderEmptyContent() {
return (
<div className='empty-con'>
<img src='https://image.xiaomaiketang.com/xm/wRDrb2pJFb.png' className='empty-img' />
<div className='empty-text'>暂无数据</div>
</div>
);
}
function handleCheckedSearchAssignor(e, type) {
const { value, checked } = e.target;
let _departmentUserVOList = [...departmentUserVOList];
let _departmentVOList = [...departmentVOList];
let _subLevelDepartmentVOList = [...subLevelDepartmentVOList];
let _checkedAssignorList = [...checkedAssignorList];
if (type === 'CUSTOMER') {
if (checked) {
_departmentUserVOList = departmentUserVOList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = true;
}
return item;
});
_checkedAssignorList.push({ ...value, checked: true });
} else {
_departmentUserVOList = departmentUserVOList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = false;
}
return item;
});
_checkedAssignorList = _checkedAssignorList.filter((item) => item.checkedId !== value.checkedId);
}
setDepartmentUserVOList(_departmentUserVOList);
}
if (type === 'DEPART') {
if (checked) {
_departmentVOList = departmentVOList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = true;
}
return item;
});
_checkedAssignorList.push({ ...value, checked: true });
} else {
_departmentVOList = departmentVOList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = false;
}
return item;
});
_checkedAssignorList = _checkedAssignorList.filter((item) => item.checkedId !== value.checkedId);
}
setDepartmentVOList(_departmentVOList);
}
if (type === 'GROUP') {
if (checked) {
_subLevelDepartmentVOList = subLevelDepartmentVOList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = true;
}
return item;
});
_checkedAssignorList.push({ ...value, checked: true });
} else {
_subLevelDepartmentVOList = subLevelDepartmentVOList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = false;
}
return item;
});
_checkedAssignorList = subLevelDepartmentVOList.filter((item) => item.checkedId !== value.checkedId);
}
setSubLevelDepartmentVOList(_subLevelDepartmentVOList);
}
const _checkedAssignorKeys = handleCheckedKeys(structureData, _checkedAssignorList, []);
setCheckedAssignorKeys(_checkedAssignorKeys);
setCheckedAssignorList(_checkedAssignorList);
}
// 弱提示渲染
function handlePlaceHolder() {
let placeholder = '';
switch (activeKey) {
case 'departMentTab':
placeholder = '搜索学员、部门';
break;
case 'postGrouptab':
placeholder = '搜索学员、岗位、岗位组';
break;
case 'customGroupTab':
placeholder = '搜索学员、分组、分组集合';
break;
default:
break;
}
return placeholder;
}
function handleDepName(depArray) {
const depArrayDom = depArray.map((item, index) => {
return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
</span>
);
});
return depArrayDom;
}
function renderOptions() {
return (
<div className='left-search-dropdown' id='left-search-dropdown'>
{departmentUserVOList.length > 0 && (
<div>
<div>学员</div>
{departmentUserVOList.map((record) => {
return (
<Checkbox checked={record.checked} value={record} key={record.checkedId} onChange={(e) => handleCheckedSearchAssignor(e, 'CUSTOMER')}>
<div className='search-result-item' depId={record.checkedId} type={type}>
<div className='search-result-item__left'>
<WWOpenDataCom type='userName' openid={record.userName} />
</div>
<div className='search-result-item__right'>
<Tooltip title={<div>{handleDepName(record.depNamesList)}</div>} placement='top' arrowPointAtCenter>
{record.depNamesList.map((item, index) => {
return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
{index < record.depNamesList.length - 1 ? ';' : ''}
</span>
);
})}
</Tooltip>
</div>
</div>
</Checkbox>
);
})}
</div>
)}
{subLevelDepartmentVOList.length > 0 && (
<div>
<div>{DeptTypeEnum[activeKey][0]}</div>
{subLevelDepartmentVOList.map((record) => {
return (
<Checkbox checked={record.checked} value={record} key={record.checkedId} onChange={(e) => handleCheckedSearchAssignor(e, 'GROUP')}>
<div className='search-result-item' depId={record.checkedId} type={type}>
<div className='search-result-item__left'>{<WWOpenDataCom type='departmentName' openid={record.name} />}</div>
</div>
</Checkbox>
);
})}
</div>
)}
{departmentVOList.length > 0 && (
<div>
<div>{activeKey === 'departMentTab' ? DeptTypeEnum[activeKey][0] : DeptTypeEnum[activeKey][1]}</div>
{departmentVOList.map((record) => {
return (
<Checkbox
disabled={record.disabled}
checked={record.checked}
value={record}
key={record.checkedId}
onChange={(e) => handleCheckedSearchAssignor(e, 'DEPART')}>
<div className='search-result-item' depId={record.checkedId} type={type}>
<div className='search-result-item__left'>
{activeKey === 'departMentTab' ? <WWOpenDataCom type='departmentName' openid={record.name} /> : <span>{record.name}</span>}
</div>
<div className='search-result-item__right'>{activeKey === 'postGrouptab' && <span>{record.parentName}</span>}</div>
</div>
</Checkbox>
);
})}
</div>
)}
{departmentUserVOList.length === 0 && departmentVOList.length === 0 && subLevelDepartmentVOList.length === 0 && renderEmptyContent()}
</div>
);
}
function documentClick() {
document.onclick = function (e) {
let _con = $('#left-search-dropdown');
if (!_con.is(e.target) && _con.has(e.target).length === 0) {
setDropDownVisible(false);
}
};
}
return (
<Modal
className='choose-assignor-modal'
title='添加指派学员'
visible={true}
onCancel={props.onClose}
onOk={() => {
props.onConfirm(checkedAssignorList);
}}
width={680}
maskClosable={false}>
<div className='assignor-container'>
<div className='left-list'>
<Search
style={{ width: 272 }}
placeholder={handlePlaceHolder()}
value={queryName}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
onChange={(e) => {
setQueryName(e.target.value);
setDropDownVisible(!!e.target.value);
}}
/>
{dropDownVisible && renderOptions()}
<div className='data-body'>
<Tabs size={'small'} onChange={(key) => setActiveKey(key)}>
<TabPane key='departMentTab' tab='部门'></TabPane>
<TabPane key='postGrouptab' tab='岗位组'></TabPane>
<TabPane key='customGroupTab' tab='自定义分组'></TabPane>
</Tabs>
<div className='tree-con'>
<Choose>
<When condition={structureData.length > 0}>
<DirectoryTree
checkable
checkStrictly
showIcon={false}
treeData={structureData}
checkedKeys={checkedAssignorKeys}
onCheck={(key, e) => onCheckAssignor(key, e)}
titleRender={(nodeData) => {
return (
<div className='node-title-div'>
{nodeData.checkedType === 'CUSTOMER' ? (
<div>
<span className='icon iconfont title-icon'>&#xe603;</span>
<span className='title-name'>
<WWOpenDataCom type='userName' openid={nodeData.checkedName} />
</span>
</div>
) : (
<div>
<span className='icon iconfont title-icon'>&#xe604;</span>
<span className='title-name'>
<WWOpenDataCom type='departmentName' openid={nodeData.checkedName} />
</span>
</div>
)}
</div>
);
}}
/>
</When>
<Otherwise>
<LottieIcon title='搜索无结果' type='search' size={150} />
</Otherwise>
</Choose>
</div>
</div>
</div>
<div className='right-list'>
<div className='header-line'>
<span className='tip-text'>已选择</span>
<span className='clear-btn' onClick={clearCheckedAssignor}>
清空
</span>
</div>
<div className='data-body'>
<Choose>
<When condition={checkedAssignorList.length > 0}>
{checkedAssignorList.map((item, index) => {
return (
<div className='selected-item'>
<span className='item-title'>
{item.checkedType === 'CUSTOMER' ? (
<div>
<span className='icon iconfont title-icon'>&#xe603;</span>
<Tooltip title={<WWOpenDataCom type='userName' openid={item.checkedName} />}>
<span className='title-name'>
<WWOpenDataCom type='userName' openid={item.checkedName} />
</span>
</Tooltip>
</div>
) : (
<div>
<span className='icon iconfont title-icon'>&#xe604;</span>
<Tooltip title={<WWOpenDataCom type='departmentName' openid={item.checkedName} />}>
<span className='title-name'>
<WWOpenDataCom type='departmentName' openid={item.checkedName} />
</span>
</Tooltip>
</div>
)}
</span>
<span className='icon iconfont clear-icon' onClick={() => removeCheckedAssignor(item)}>
&#xe717;
</span>
</div>
);
})}
</When>
<Otherwise>
<LottieIcon title='暂无数据' type='college' size={150} />
</Otherwise>
</Choose>
</div>
</div>
</div>
</Modal>
);
}
export default ChooseAssignorModal;
.choose-assignor-modal {
.assignor-container {
display: flex;
height: 417px;
.left-list {
position: relative;
width: 50%;
margin-right: 24px;
padding: 12px 0 12px 16px;
border: 1px solid #e8e8e8;
.ant-select-auto-complete .ant-select-clear {
font-size: 14px;
right: 15px;
}
.left-search-dropdown {
position: absolute;
padding: 16px;
background: #fff;
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.08);
z-index: 999;
border-radius: 2px;
width: 274px;
max-height: 280px;
overflow-y: scroll;
.catalog-title {
font-size: 14px;
color: #666;
margin-bottom: 14px;
}
.search-result-item {
padding: 14px 0;
color: #333;
width: 220px;
display: flex;
justify-content: space-between;
.title-icon {
color: #999;
margin-right: 3px;
}
.search-result-item__left {
font-size: 14px;
}
.search-result-item__right {
font-size: 14px;
width: 84px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #999;
text-align: right;
}
}
.ant-select-dropdown-menu-item-group-title {
color: #666;
font-weight: bold;
}
.ant-select-item-option-grouped {
padding-left: 12px;
}
.empty-con {
text-align: center;
.empty-img {
width: 150px;
height: 150px;
}
.empty-text {
color: #666;
}
}
}
.data-body {
.ant-tabs-nav {
margin: 0 !important;
.ant-tabs-tab {
padding: 16px 0 10px 0 !important;
font-size: 14px !important;
}
}
.lottie-icon {
margin-top: 64px;
.lottie-icon__title {
color: #666666;
}
}
.tree-con {
overflow-y: scroll;
overflow-x: hidden;
max-height: 323px;
padding-right: 16px;
.ant-tree .ant-tree-treenode {
padding: 12px 0 !important;
}
.node-title-div {
display: flex;
justify-content: space-between;
white-space: nowrap;
.title-icon {
margin-right: 6px;
color: #999999;
}
.title-name {
position: absolute;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 55%;
left: 28px;
color: #666666;
}
}
.ant-tree.ant-tree-directory {
.ant-tree-treenode-selected:hover::before,
.ant-tree-treenode-selected::before {
background: #f3f6fa;
}
}
.ant-tree-switcher {
color: #999999 !important;
}
}
}
}
.right-list {
width: 50%;
border: 1px solid #e8e8e8;
.header-line {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #e8e8e8;
padding: 12px 16px;
.tip-text {
font-weight: 500;
color: #333333;
}
.clear-btn {
color: #2966ff;
cursor: pointer;
}
}
.data-body {
overflow-y: scroll;
overflow-x: hidden;
max-height: 370px;
.lottie-icon {
margin-top: 76px;
.lottie-icon__title {
color: #666666;
}
}
.selected-item {
padding: 12px 12px 12px 16px;
display: flex;
justify-content: space-between;
.item-title {
width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.title-icon {
color: #999999;
margin-right: 6px;
}
.title-name {
color: #333333;
}
}
.clear-icon {
font-size: 16px;
color: #999999;
vertical-align: middle;
cursor: pointer;
}
}
}
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-12 16:27:38
* @LastEditors: yuananting
* @LastEditTime: 2021-08-17 19:10:02
* @Description: 新建培训任务-选择协同人员
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { Modal, Input, Tooltip, List, Checkbox } from 'antd';
import StoreService from '@/domains/store-domain/storeService';
import './ChooseCollaboratorModal.less';
import WWOpenDataCom from '@/components/WWOpenDataCom';
import LottieIcon from '@/components/LottieIcon';
import _ from 'underscore';
const { Search } = Input;
function ChooseCollaboratorModal(props) {
const [searchKey, setSearchKey] = useState(''); // 搜索内容
const [employeeList, setEmployeeList] = useState([]); // 员工列表
const [checkedCollaboratorList, setCheckedCollaboratorList] = useState(props.currentCollaboratorList || []); // 勾选的协同人员
const [checkedCollaboratorKeys, setCheckedCollaboratorKeys] = useState(props.currentCollaboratorList.map((item) => item.checkedId) || []);
const [allChecked, setAllChecked] = useState(false);
useEffect(() => {
getEmployeeList();
}, [searchKey]);
function getEmployeeList() {
const params = {
current: 1,
size: 999,
nickName: searchKey,
phone: '',
roleCodes: [],
};
StoreService.getEmployeeList(params).then((res) => {
const { records } = res.result;
records.map((item) => {
item.checkedName = item.nickName;
item.checkedId = item.id;
item.checked = checkedCollaboratorKeys.includes(item.checkedId);
return item;
});
const checkedEmployeeList = records.filter((item) => item.checked);
const _checkedAll = records.length === checkedEmployeeList.length;
setEmployeeList(records);
setAllChecked(_checkedAll);
});
}
function renderDepItem(record) {
if (!record.depNameList) {
return <span>-</span>;
}
return record.depNameList.map((item, index) => {
return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
{index < record.depNameList.length - 1 ? '、' : ''}
</span>
);
});
}
function handleCheckedCollaborator(e) {
const { value, checked } = e.target;
let _checkedCollaboratorList = [...checkedCollaboratorList];
let _employeeList = [];
if (checked) {
_employeeList = employeeList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = true;
}
return item;
});
_checkedCollaboratorList.push({ ...value, checked: true });
} else {
_employeeList = employeeList.map((item) => {
if (item.checkedId === value.checkedId) {
item.checked = false;
}
return item;
});
_checkedCollaboratorList = checkedCollaboratorList.filter((item) => item.checkedId !== value.checkedId);
}
const _checkedCollaboratorKeys = _checkedCollaboratorList.map((item) => item.checkedId);
const checkedEmployeeList = _employeeList.filter((item) => item.checked);
const _checkedAll = employeeList.length === checkedEmployeeList.length;
setEmployeeList(_employeeList);
setCheckedCollaboratorList(_checkedCollaboratorList);
setCheckedCollaboratorKeys(_checkedCollaboratorKeys);
setAllChecked(_checkedAll);
}
function removeCheckedCollaborator(record) {
const _employeeList = employeeList.map((item) => {
if (item.checkedId === record.checkedId) {
item.checked = false;
}
return item;
});
const _checkedCollaboratorList = checkedCollaboratorList.filter((item) => item.checkedId !== record.checkedId);
const _checkedCollaboratorKeys = _checkedCollaboratorList.map((item) => item.checkedId);
const checkedEmployeeList = _employeeList.filter((item) => item.checked);
const _checkedAll = employeeList.length === checkedEmployeeList.length;
setAllChecked(_checkedAll);
setCheckedCollaboratorList(_checkedCollaboratorList);
setCheckedCollaboratorKeys(_checkedCollaboratorKeys);
setEmployeeList(_employeeList);
}
function clearCheckedCollaborator() {
const _employeeList = employeeList.map((item) => {
item.checked = item.checkedId === props.createId;
return item;
});
const createrItem = checkedCollaboratorList.filter((item) => item.checkedId === props.createId);
setEmployeeList(_employeeList);
setCheckedCollaboratorList(createrItem);
setCheckedCollaboratorKeys([props.createId]);
setAllChecked(false);
}
function handleCheckedAll(e) {
let _employeeList = [];
let _checkedCollaboratorList = [];
if (e.target.checked) {
_employeeList = employeeList.map((item) => {
item.checked = true;
return item;
});
const filterList = employeeList.filter((item) => {
return !checkedCollaboratorKeys.includes(item.checkedId);
});
_checkedCollaboratorList = checkedCollaboratorList.concat([...filterList]);
} else {
_employeeList = employeeList.map((item) => {
item.checked = item.checkedId === props.createId;
return item;
});
const removeIds = _employeeList.filter((item) => !item.checked).map((childItem) => childItem.checkedId);
_checkedCollaboratorList = checkedCollaboratorList.filter((item) => {
return !removeIds.includes(item.checkedId);
});
}
const _checkedCollaboratorKeys = _checkedCollaboratorList.map((item) => item.checkedId);
setEmployeeList(_employeeList);
setCheckedCollaboratorList(_checkedCollaboratorList);
setCheckedCollaboratorKeys(_checkedCollaboratorKeys);
setAllChecked(e.target.checked);
}
return (
<Modal
className='choose-collaborator-modal'
title='选择协同者'
visible={true}
onCancel={props.onClose}
onOk={() => {
props.onConfirm(checkedCollaboratorList);
props.onClose();
}}
width={680}
maskClosable={false}>
<div className='collaborator-container'>
<div className='left-list'>
<Search
placeholder='搜索员工'
value={searchKey}
style={{ paddingRight: 16 }}
onChange={(e) => setSearchKey(e.target.value.trim())}
onSearch={getEmployeeList}
className='search search-input'
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
<div className='data-body'>
<Choose>
<When condition={employeeList.length > 0}>
<List
header={
<Checkbox checked={allChecked} onChange={handleCheckedAll}>
全部
</Checkbox>
}
dataSource={employeeList}
renderItem={(item, index) => (
<List.Item>
<Checkbox
checked={item.checked}
value={item}
key={item.checkedId}
disabled={item.checkedId === props.createId}
onChange={handleCheckedCollaborator}>
<div className='employee-item'>
<span className='item-title'>
<span className='icon iconfont title-icon'>&#xe84a;</span>
<Tooltip title={<WWOpenDataCom type='userName' openid={item.checkedName} />}>
<span className='title-name'>
<WWOpenDataCom type='userName' openid={item.checkedName} />
</span>
</Tooltip>
</span>
<span className='dep-info'>{renderDepItem(item)}</span>
</div>
</Checkbox>
</List.Item>
)}
/>
</When>
<Otherwise>
<LottieIcon title='搜索无结果' type='search' size={150} />
</Otherwise>
</Choose>
</div>
</div>
<div className='right-list'>
<div className='header-line'>
<span className='tip-text'>已选择</span>
<span className='clear-btn' onClick={clearCheckedCollaborator}>
清空
</span>
</div>
<div className='data-body'>
{checkedCollaboratorList.map((item, index) => {
return (
<div className='selected-item'>
<span className='item-title'>
<span className='icon iconfont title-icon'>&#xe84a;</span>
<Tooltip title={<WWOpenDataCom type='userName' openid={item.checkedName} />}>
<span className='title-name'>
<WWOpenDataCom type='userName' openid={item.checkedName} />
</span>
</Tooltip>
</span>
<span
className={`icon iconfont clear-icon ${item.checkedId === props.createId && 'disabled'}`}
onClick={() => removeCheckedCollaborator(item)}>
&#xe717;
</span>
</div>
);
})}
</div>
</div>
</div>
</Modal>
);
}
export default ChooseCollaboratorModal;
.choose-collaborator-modal {
.collaborator-container {
display: flex;
height: 417px;
.left-list {
width: 50%;
margin-right: 24px;
padding: 12px 0 12px 16px;
border: 1px solid #e8e8e8;
.data-body {
overflow-y: scroll;
overflow-x: hidden;
max-height: 370px;
width: 288px;
.lottie-icon {
margin-top: 76px;
.lottie-icon__title {
color: #666666;
}
}
.ant-list {
.ant-list-item:hover {
background-color: #f3f6fa;
}
}
.employee-item {
display: flex;
justify-content: space-between;
.item-title {
width: 110px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.title-icon {
color: #0081f0;
margin-right: 6px;
}
.title-name {
color: #333333;
}
}
.dep-info {
width: 128px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 12px;
text-align: right;
}
}
}
}
.right-list {
width: 50%;
border: 1px solid #e8e8e8;
.header-line {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #e8e8e8;
padding: 12px 16px;
.tip-text {
font-weight: 500;
color: #333333;
}
.clear-btn {
color: #2966ff;
cursor: pointer;
}
}
.data-body {
overflow-y: scroll;
overflow-x: hidden;
max-height: 370px;
.selected-item {
padding: 12px 12px 12px 16px;
display: flex;
justify-content: space-between;
.item-title {
width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.title-icon {
color: #0081f0;
margin-right: 6px;
}
.title-name {
color: #333333;
}
}
.clear-icon {
font-size: 16px;
color: #999999;
vertical-align: middle;
cursor: pointer;
&.disabled {
color: #ccc;
cursor: not-allowed;
pointer-events: none;
}
}
}
}
}
}
}
/*
* @Author: 吴文洁
* @Date: 2020-07-20 19:12:49
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-20 20:25:13
* @Description: 大班直播分享弹窗
*/
import React from 'react';
import { Modal, Button, message } from 'antd';
import domtoimage from 'dom-to-image';
import qrcode from '@/libs/qrcode/qrcode.js';
import User from '@/common/js/user';
import $ from 'jquery';
import CourseService from '@/domains/course-domain/CourseService';
import './ShareTrainTaskModal.less';
const DEFAULT_COVER = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
class ShareTrainTaskModal extends React.Component {
constructor(props) {
super(props);
this.state = {
shareUrl: '',
};
}
componentDidMount() {
// 获取短链接
this.handleConvertShortUrl();
}
handleConvertShortUrl = () => {
const { longUrl } = this.props.data;
// 发请求
CourseService.getQrcode({
urls: [longUrl],
}).then((res) => {
const { result = [] } = res;
this.setState(
{
shareUrl: result[0].shortUrl,
},
() => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new qrcode({
text: this.state.shareUrl,
size: 98,
});
qrcodeWrapDom.appendChild(qrcodeNode);
const qrcodeWrapDomDownload = document.querySelector('#qrcodeWrap-dowload');
const qrcodeNodeDownLoad = new qrcode({
text: this.state.shareUrl,
size: 196,
});
qrcodeWrapDomDownload.appendChild(qrcodeNodeDownLoad);
}
);
});
};
componentWillUnmount() {
// 页面销毁之前清空定时器
clearTimeout(this.timer);
}
// 下载海报
handleDownloadPoster = () => {
this.setState(
{
showImg: true,
time: new Date().valueOf(),
},
() => {
this.setState({ time: new Date().valueOf() }, () => {
let node = document.getElementById('poster-dowload');
domtoimage.toPng(node).then((imgData) => {
const download = document.createElement('a');
const { planName } = this.props.data;
$(download).attr('href', imgData).attr('download', `${planName}.png`).get(0).click();
});
});
}
);
};
// 复制分享链接
handleCopy = () => {
const textContent = document.getElementById('shareUrl').innerText;
window.copyText(textContent);
message.success('复制成功!');
};
render() {
const { data } = this.props;
const { planName, coverUrl = DEFAULT_COVER } = data;
const { shareUrl, showImg, time } = this.state;
return (
<Modal
title={'分享培训任务'}
width={680}
visible={true}
footer={null}
maskClosable={false}
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}
className='share-task-modal'
onCancel={this.props.close}>
<div className='left'>
<div id='poster'>
<div className='store-name'>
<span className='text'>{User.getStoreName()}</span>
</div>
<div className='course-name-title'>邀请你参与培训:</div>
<div className='live-couse-name'>{planName}</div>
<Choose>
<When condition={showImg}>
<img crossOrigin='*' src={coverUrl + `?=${time}`} className='course-cover' alt='' />
</When>
<Otherwise>
<img src={coverUrl + `?=${time}`} className='course-cover' alt='' />
</Otherwise>
</Choose>
<div className='qrcode-wrap'>
<div className='qrcode-wrap__left'>
<div className='text'>长按识别二维码进入观看</div>
<img className='finger' src='https://image.xiaomaiketang.com/xm/thpkWDwJsC.png' alt='' />
</div>
<div className='qrcode-wrap__right' id='qrcodeWrap'></div>
</div>
</div>
<div id='poster-dowload'>
<div className='store-name'>
<span className='text'>{User.getStoreName()}</span>
</div>
<div className='course-name-title'>邀请你参与培训:</div>
<div className='live-couse-name'>{planName}</div>
<Choose>
<When condition={showImg}>
<img crossOrigin='*' src={coverUrl + `?=${time}`} className='course-cover' alt='' />
</When>
<Otherwise>
<img src={coverUrl + `?=${time}`} className='course-cover' alt='' />
</Otherwise>
</Choose>
<div className='qrcode-wrap'>
<div className='qrcode-wrap__left'>
<div className='text'>长按识别二维码进入观看</div>
<img className='finger' src='https://image.xiaomaiketang.com/xm/thpkWDwJsC.png' alt='' />
</div>
<div className='qrcode-wrap__right' id='qrcodeWrap-dowload'></div>
</div>
</div>
</div>
<div className='right'>
<div className='share-poster right__item'>
<div className='title'>① 海报分享</div>
<div className='sub-title'>学员可通过微信扫描海报二维码,查看培训计划</div>
<div className='content' onClick={this.handleDownloadPoster}>
下载海报
</div>
</div>
<div className='share-url right__item'>
<div className='title'>② 链接分享</div>
<div className='sub-title'>学员可通过微信或浏览器打开以下链接,查看培训计划</div>
<div className='content url-content'>
<div className='share-url' id='shareUrl'>
{shareUrl}
</div>
<Button type='primary' onClick={this.handleCopy}>
复制
</Button>
</div>
</div>
</div>
</Modal>
);
}
}
export default ShareTrainTaskModal;
.share-task-modal {
.ant-modal-body {
display: flex;
height: 510px !important;
overflow: hidden !important;
.left {
width: 303px;
margin: 0 32px 0 16px;
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.05);
border-radius: 12px;
#poster {
background: #fff;
margin: 0;
padding: 20px;
margin-bottom: 140px;
.course-name-title {
font-size: 14px;
color: #333;
line-height: 20px;
margin-bottom: 4px;
}
.live-couse-name {
font-size: 16px;
color: #333333;
font-weight: 600;
}
.course-name {
color: #333;
font-size: 16px;
font-weight: 600;
line-height: 20px;
}
.course-cover {
width: 263px;
height: 143px;
border-radius: 6px;
margin-top: 8px;
}
.qrcode-wrap {
padding: 0 16px;
display: flex;
align-items: center;
margin: 24px 0 16px 0;
&__left {
width: 98px;
text-align: center;
margin-right: 22px;
.text {
line-height: 20px;
}
.finger {
width: 40px;
height: 40px;
margin-top: 8px;
}
}
&__right {
width: 110px;
height: 110px;
padding: 6px;
}
}
.store-name {
// padding: 8px 16px;
display: flex;
align-items: center;
margin-bottom: 8px;
.text {
font-size: 12px;
color: #999;
font-size: 14px;
line-height: 20px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
}
}
#poster-dowload {
background: #fff;
margin: 0;
padding: 40px;
width: 606px;
.course-name-title {
font-size: 28px;
color: #333;
line-height: 40px;
margin-bottom: 8px;
}
.live-couse-name {
font-size: 32px;
color: #333333;
font-weight: 600;
}
.course-name {
color: #333;
font-size: 32px;
font-weight: 600;
line-height: 40px;
}
.course-cover {
width: 526px;
height: 286px;
border-radius: 6px;
margin-top: 16px;
}
.qrcode-wrap {
padding: 0 32px;
display: flex;
align-items: center;
margin: 48px 0 32px 0;
&__left {
width: 196px;
text-align: center;
margin-right: 44px;
.text {
line-height: 40px;
}
.finger {
width: 80px;
height: 80px;
margin-top: 16px;
}
}
&__right {
width: 220px;
height: 220px;
padding: 12px;
}
}
.store-name {
// padding: 8px 16px;
display: flex;
align-items: center;
margin-bottom: 16px;
.text {
font-size: 12px;
color: #999;
font-size: 28px;
line-height: 40px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
}
}
}
.right {
.title {
color: #333;
font-weight: 500;
}
.sub-title {
color: #999;
margin-top: 16px;
}
.content {
display: flex;
align-items: center;
margin-top: 8px;
.share-url {
width: 212px;
overflow: hidden;
height: 28px;
line-height: 28px;
border-radius: 4px 0 0 4px;
padding-left: 12px;
white-space: nowrap;
color: #999999;
background: #efefef;
}
.ant-btn {
margin-left: -2px;
}
}
.url-content {
position: relative;
&:after {
content: '';
width: 12px;
height: 22px;
background: #efefef;
position: absolute;
right: 71px;
}
}
.share-poster {
margin-bottom: 40px;
.content {
color: #2966ff;
cursor: pointer;
}
}
}
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: yuananting * @Author: yuananting
* @Date: 2021-02-23 18:28:50 * @Date: 2021-02-23 18:28:50
* @LastEditors: wufan * @LastEditors: wufan
* @LastEditTime: 2021-07-28 16:20:05 * @LastEditTime: 2021-09-03 10:36:33
* @Description: 助学工具-课程分类 * @Description: 助学工具-课程分类
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -566,7 +566,7 @@ class CourseCategoryManage extends Component { ...@@ -566,7 +566,7 @@ class CourseCategoryManage extends Component {
新增一级分类 新增一级分类
</Button> </Button>
<div className='show-tips' style={{ marginTop: '12px', width: '900px' }}> <div className='show-tips' style={{ marginTop: '12px', width: '900px' }}>
<ShowTips message='为方便管理,该分类用于课程、培训计划、题库、知识库等模块,改动将同步各模块更新' /> <ShowTips message='为方便管理,该分类用于课程、题库、知识库等模块,改动将同步各模块更新' />
</div> </div>
<div className='course-category-tree'> <div className='course-category-tree'>
{ {
......
...@@ -2,43 +2,50 @@ ...@@ -2,43 +2,50 @@
* @Author: yuananting * @Author: yuananting
* @Date: 2021-06-16 10:14:37 * @Date: 2021-06-16 10:14:37
* @LastEditors: yuananting * @LastEditors: yuananting
* @LastEditTime: 2021-07-27 14:04:03 * @LastEditTime: 2021-08-18 17:40:48
* @Description: 描述一下咯 * @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import React, { useState, useRef, useEffect, useContext } from 'react' import React, { useState, useRef, useEffect } from 'react';
import { message, Modal } from 'antd'; import { message, Modal } from 'antd';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import PaperContent from '../paper-manage/PaperContent' import PaperContent from '../paper-manage/PaperContent';
import './AddExam.less'; import './AddExam.less';
import user from '@/common/js/user';
function SelectPaperModal(props: any) { function SelectPaperModal(props: any) {
const [item, setItem] = useState(props.paperInfo);
const itemRef = useRef({});
const [item, setItem] = useState(props.paperInfo); useEffect(() => {
const itemRef = useRef({}) itemRef.current = item;
}, [item]);
useEffect(() => { return (
itemRef.current = item <Modal
}, [item]) maskClosable={false}
width={1080}
return <Modal title='选择试卷'
maskClosable={false} visible={true}
width={1080} centered={true}
title="选择试卷" onOk={() => {
visible={true} if (Object.keys(itemRef.current).length < 2) {
centered={true} // 培训任务中试卷对象默认长度为0,考试中试卷对象默认长度为1
onOk={() => { return message.warning('请选择试卷');
props.onSelect(itemRef.current);
props.close();
}
} }
onCancel={() => { props.close() }} props.onSelect(itemRef.current);
> props.close();
<PaperContent paperId={item.paperId} onSelect={(item: any) => { setItem(item) }} type='modal-select'></PaperContent> }}
onCancel={() => {
</Modal > props.close();
}}>
<PaperContent
paperId={item.paperId}
onSelect={(item: any) => {
setItem(item);
}}
type='modal-select'></PaperContent>
</Modal>
);
} }
export default withRouter(SelectPaperModal);
export default withRouter(SelectPaperModal);
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: yuananting * @Author: yuananting
* @Date: 2021-04-07 16:10:21 * @Date: 2021-04-07 16:10:21
* @LastEditors: wufan * @LastEditors: wufan
* @LastEditTime: 2021-04-26 10:21:19 * @LastEditTime: 2021-08-10 14:19:22
* @Description: 助学工具-考试-考试结果 * @Description: 助学工具-考试-考试结果
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -18,6 +18,7 @@ import Breadcrumbs from '@/components/Breadcrumbs'; ...@@ -18,6 +18,7 @@ import Breadcrumbs from '@/components/Breadcrumbs';
function TestDetailPage(props) { function TestDetailPage(props) {
const examId = props.match.params.testId.replace(/\?.+/, ''); const examId = props.match.params.testId.replace(/\?.+/, '');
const paperId = window.getParameterByName('paperId'); const paperId = window.getParameterByName('paperId');
const fromTrainingTask = window.getParameterByName('fromTrainingTask'); // 是否来自培训任务
const userId = window.getParameterByName('userId'); const userId = window.getParameterByName('userId');
const [examDetail, setExamDetail] = useState({ const [examDetail, setExamDetail] = useState({
answerAnalysis: '', answerAnalysis: '',
...@@ -41,6 +42,7 @@ function TestDetailPage(props) { ...@@ -41,6 +42,7 @@ function TestDetailPage(props) {
const [questionList, setQuestionList] = useState([]); // 试卷题目列表 const [questionList, setQuestionList] = useState([]); // 试卷题目列表
const [userAnswerList, setUserAnswerList] = useState([]); // 用户答案列表 const [userAnswerList, setUserAnswerList] = useState([]); // 用户答案列表
const [isScrollShow, setIsScrollShow] = useState(false); // 是否展示回到顶部按钮 const [isScrollShow, setIsScrollShow] = useState(false); // 是否展示回到顶部按钮
const [isShowErrorPage, setIsShowErrorPage] = useState(false); // 是否展示异常提示页面
const { answerAnalysis, resultContent, examName, totalScore, totalQuestionCount, passScore, examDuration, userExamDuration, userScore, userCorrectQuestion } = const { answerAnalysis, resultContent, examName, totalScore, totalQuestionCount, passScore, examDuration, userExamDuration, userScore, userCorrectQuestion } =
examDetail; examDetail;
...@@ -211,16 +213,20 @@ function TestDetailPage(props) { ...@@ -211,16 +213,20 @@ function TestDetailPage(props) {
}); });
return ( return (
<div className='exam-result-page page'> <div className={`${fromTrainingTask ? 'exam-result-page' :'exam-result-page page'}`}>
<Breadcrumbs navList={'答题详情'} goBack={props.history.goBack} /> {fromTrainingTask ? null : <Breadcrumbs navList={'答题详情'} goBack={props.history.goBack} />}
<div className='center'> <div className='center'>
<div className='box'> <div className='box'>
<img
className="header-img"
src="https://image.xiaomaiketang.com/xm/6Kt6PkJcWa.png"
/>
<div className='box-content'> <div className='box-content'>
<div <div
className='exam-head' className='exam-head'
style={{ style={{
padding: examName.length > 20 ? '8px 0 14px' : '16px 0 29px', padding: examName.length > 20 ? '8px 0 14px' : '26px 0 29px',
}}> }}>
<div className={`exam-name ${examName.length > 20 ? 'many' : 'few'}`}>{examName}</div> <div className={`exam-name ${examName.length > 20 ? 'many' : 'few'}`}>{examName}</div>
</div> </div>
......
...@@ -19,6 +19,14 @@ ...@@ -19,6 +19,14 @@
} }
} }
.box { .box {
position: relative;
.header-img {
height: 180px;
width: calc(100vw - 450px);
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.box-content { .box-content {
position: relative; position: relative;
width: 840px; width: 840px;
...@@ -40,6 +48,7 @@ ...@@ -40,6 +48,7 @@
.few { .few {
font-size: 25px; font-size: 25px;
line-height: 36px; line-height: 36px;
color: #fff;
} }
} }
.empty-result { .empty-result {
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-04-29 10:26:32 * @Date: 2020-04-29 10:26:32
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-08-11 20:27:02 * @LastEditTime: 2021-09-03 10:33:05
* @Description: 内容线路由配置 * @Description: 内容线路由配置
*/ */
import Home from '@/modules/home/Home'; import Home from '@/modules/home/Home';
...@@ -23,19 +23,21 @@ import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGr ...@@ -23,19 +23,21 @@ import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGr
import AddOfflineCoursePage from '@/modules/course-manage/offline-course/AddOfflineCourse'; import AddOfflineCoursePage from '@/modules/course-manage/offline-course/AddOfflineCourse';
// import DataList from '@/modules/course-manage/DataList/DataList'; // import DataList from '@/modules/course-manage/DataList/DataList';
// import ClassBook from '@/modules/resource-disk'; // import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk' import ResourceDisk from '@/modules/resource-disk';
import SwitchRoute from '@/modules/root/SwitchRoute' import SwitchRoute from '@/modules/root/SwitchRoute';
import PlanPage from '@/modules/plan-manage/PlanPage' import PlanPage from '@/modules/plan-manage/PlanPage';
import AddPlanPage from '@/modules/plan-manage/AddPlan' import AddPlanPage from '@/modules/plan-manage/AddPlan';
import LearningDataPage from '@/modules/plan-manage/LearningData' import LearningDataPage from '@/modules/plan-manage/LearningData';
import StoreInfoPage from '@/modules/store-manage/StoreInfo' import StoreInfoPage from '@/modules/store-manage/StoreInfo';
import CourseCategoryManage from '@/modules/teach-tool/components/CourseCategoryManage' import CourseCategoryManage from '@/modules/teach-tool/components/CourseCategoryManage';
import QuestionManageIndex from '@/modules/teach-tool/question-manage/Index' import QuestionManageIndex from '@/modules/teach-tool/question-manage/Index';
import PaperManageIndex from '@/modules/teach-tool/paper-manage/Index' import PaperManageIndex from '@/modules/teach-tool/paper-manage/Index';
import ExaminationManagerIndex from '@/modules/teach-tool/examination-manager/Index' import ExaminationManagerIndex from '@/modules/teach-tool/examination-manager/Index';
import ExaminationManagerTestDetail from '@/modules/teach-tool/examination-manager/TestDetailPage' import ExaminationManagerTestDetail from '@/modules/teach-tool/examination-manager/TestDetailPage';
import KnowledgeBase from '@/modules/knowledge-base/index' import KnowledgeBase from '@/modules/knowledge-base/index';
import CollegeInfoPage from '@/modules/college-manage/CollegeInfoPage' import CollegeInfoPage from '@/modules/college-manage/CollegeInfoPage';
import TrainTaskPage from '@/modules/task-center/train-task/TrainTaskPage';
import AddTrainTask from '@/modules/task-center/train-task/AddTrainTask';
const mainRoutes = [ const mainRoutes = [
{ {
...@@ -161,6 +163,16 @@ const mainRoutes = [ ...@@ -161,6 +163,16 @@ const mainRoutes = [
name: '培训计划', name: '培训计划',
}, },
{ {
path: '/train-task',
component: TrainTaskPage,
name: '培训任务',
},
{
path: '/create-train-task',
component: AddTrainTask,
name: '创建培训任务',
},
{
path: '/create-plan', path: '/create-plan',
component: AddPlanPage, component: AddPlanPage,
name: '创建线上课', name: '创建线上课',
...@@ -180,6 +192,6 @@ const mainRoutes = [ ...@@ -180,6 +192,6 @@ const mainRoutes = [
component: LearningDataPage, component: LearningDataPage,
name: '学习数据', name: '学习数据',
}, },
] ];
export default mainRoutes export default mainRoutes;
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-02-21 15:53:31 * @Date: 2021-02-21 15:53:31
* @LastEditors: wufan * @LastEditors: yuananting
* @LastEditTime: 2021-07-05 10:29:23 * @LastEditTime: 2021-07-28 11:29:17
* @Description: 描述一下咯 * @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -44,17 +44,18 @@ export const menuList: any = [ ...@@ -44,17 +44,18 @@ export const menuList: any = [
}, },
], ],
}, },
{ {
groupName: '培训管理', groupName: '任务中心',
groupCode: 'TrainManage', groupCode: 'TrainManage',
icon: '&#xe8a6;', icon: '&#xe8a6;',
img: 'https://image.xiaomaiketang.com/xm/Yy6pZ6G6kS.png', img: 'https://image.xiaomaiketang.com/xm/Yy6pZ6G6kS.png',
selectImg: 'https://image.xiaomaiketang.com/xm/Z8G6NMQhaH.png', selectImg: 'https://image.xiaomaiketang.com/xm/Z8G6NMQhaH.png',
children: [ children: [
{ {
groupName: '培训计划', groupName: '培训任务',
groupCode: 'TrainPlan', groupCode: 'TrainPlan',
link: '/plan', link: '/train-task',
}, },
], ],
}, },
......
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