Commit 74cf752f by zhujian

fix:合并任务中心代码

parents 8e50a3fb d8e8d403
@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_2digpsfgq8l.woff2?t=1628853864698') format('woff2'),
url('//at.alicdn.com/t/font_2223403_7261tsts1dc.woff?t=1628475376853') format('woff'), url('//at.alicdn.com/t/font_2223403_2digpsfgq8l.woff?t=1628853864698') format('woff'),
url('//at.alicdn.com/t/font_2223403_7261tsts1dc.ttf?t=1628475376853') format('truetype'); url('//at.alicdn.com/t/font_2223403_2digpsfgq8l.ttf?t=1628853864698') 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,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'
} }
......
...@@ -1723,5 +1723,21 @@ input:focus { ...@@ -1723,5 +1723,21 @@ input:focus {
.ant-btn { .ant-btn {
margin-left: 10px; margin-left: 10px;
} }
.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;
} }
} }
\ 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-13 18:06:35
* @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);
}
/* /*
* @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-08-09 15:52:49
* @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: yuananting
* @Date: 2021-08-06 17:32:41
* @LastEditors: yuananting
* @LastEditTime: 2021-08-13 18:07:06
* @Description: 任务中心-培训任务接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import {
getTrainingTaskPage,
getStoreTaskNum,
createTrainingExam,
createTrainingTask,
updateIssueStateTraining,
deleteTrainingTask,
getTrainingTaskDetail,
updateTrainingTask,
getTaskCustomerDetail,
updateTrainingTaskAssign,
} 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);
}
}
<!-- <!--
* @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-13 19:25:12
* @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_2digpsfgq8l.css">
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
......
declare module 'jquery' declare module 'jquery'
declare module 'cropper' declare module 'cropper'
declare module 'ExamShareModal' declare module 'ExamShareModal'
declare module 'routeHooks' declare module 'routeHooks'
declare module 'college'
declare module '@/common/lottie/college'
// 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-12 15:58:30 * @LastEditTime: 2021-08-13 19:25:18
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
--> -->
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,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_7261tsts1dc.css" /> <link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_2digpsfgq8l.css" />
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
......
...@@ -5,22 +5,23 @@ ...@@ -5,22 +5,23 @@
* @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 WechatApi from '@/common/js/wechatApi'; import WechatApi from '@/common/js/wechatApi';
import LimitTip from './LimitTip';
import WWOpenDataCom from '@/components/WWOpenDataCom'; import WWOpenDataCom from '@/components/WWOpenDataCom';
import LimitTip from "./LimitTip";
import "./EmployeeManage.less";
import NewChooseMembersModal from "./modal/NewChooseMembersModal"; import './EmployeeManage.less';
import NewChooseMembersModal from './modal/NewChooseMembersModal';
const { confirm } = Modal; const { confirm } = Modal;
const { Search } = Input; const { Search } = Input;
...@@ -34,7 +35,7 @@ interface RecordTypes { ...@@ -34,7 +35,7 @@ interface RecordTypes {
phone: string; phone: string;
avatar?: string; avatar?: string;
weChatAccount?: string; weChatAccount?: string;
depNameList:any depNameList: any;
} }
interface RoleItemType { interface RoleItemType {
...@@ -59,7 +60,7 @@ interface ChoosedItemType { ...@@ -59,7 +60,7 @@ interface ChoosedItemType {
avatar?: string; avatar?: string;
storeUserId?: string; storeUserId?: string;
weChatAccount?: string; weChatAccount?: string;
depNameList?:any; depNameList?: any;
} }
function EmployeeManage() { function EmployeeManage() {
...@@ -67,25 +68,25 @@ function EmployeeManage() { ...@@ -67,25 +68,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()) {
...@@ -95,14 +96,14 @@ function EmployeeManage() { ...@@ -95,14 +96,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();
...@@ -134,7 +135,7 @@ function EmployeeManage() { ...@@ -134,7 +135,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);
...@@ -149,29 +150,21 @@ function EmployeeManage() { ...@@ -149,29 +150,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>;
}, },
...@@ -184,8 +177,8 @@ function EmployeeManage() { ...@@ -184,8 +177,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>
...@@ -199,7 +192,7 @@ function EmployeeManage() { ...@@ -199,7 +192,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>;
}, },
}, },
{ {
...@@ -207,28 +200,14 @@ function EmployeeManage() { ...@@ -207,28 +200,14 @@ 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>
}
{record.depNameList &&
(record.depNameList.length > 0 ?
<span className="edit" onClick={() => handleEditEmployee(record)}>
编辑
</span>
:
<span className="edit edit-disable">
编辑 编辑
</span> </span>
) <span className='divider-line'>{' | '}</span>
}
<span className="divider-line">{" | "}</span>
<span <span
className="delete" className="delete"
onClick={() => { onClick={() => {
...@@ -245,9 +224,9 @@ function EmployeeManage() { ...@@ -245,9 +224,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>;
}, },
...@@ -257,15 +236,19 @@ function EmployeeManage() { ...@@ -257,15 +236,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,
...@@ -273,7 +256,7 @@ function EmployeeManage() { ...@@ -273,7 +256,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 = (
...@@ -283,13 +266,13 @@ function EmployeeManage() { ...@@ -283,13 +266,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}
...@@ -300,14 +283,12 @@ function EmployeeManage() { ...@@ -300,14 +283,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);
}, },
...@@ -325,16 +306,16 @@ function EmployeeManage() { ...@@ -325,16 +306,16 @@ function EmployeeManage() {
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>,
...@@ -344,10 +325,10 @@ function EmployeeManage() { ...@@ -344,10 +325,10 @@ function EmployeeManage() {
okText: "确定", okText: "确定",
cancelText: "取消", cancelText: "取消",
onOk: () => { onOk: () => {
confirmUpdateListData() confirmUpdateListData();
}, },
}); });
}else{ } else {
Modal.warning({ Modal.warning({
title: '提示', title: '提示',
okText: '我知道了', okText: '我知道了',
...@@ -360,34 +341,31 @@ function EmployeeManage() { ...@@ -360,34 +341,31 @@ 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
...@@ -520,11 +498,13 @@ function EmployeeManage() { ...@@ -520,11 +498,13 @@ function EmployeeManage() {
<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();
}} }}
/> />
......
...@@ -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
......
...@@ -721,7 +721,7 @@ class LiveCourseList extends React.Component { ...@@ -721,7 +721,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)}>
......
...@@ -2,40 +2,40 @@ ...@@ -2,40 +2,40 @@
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-05 10:12:45 * @Date: 2020-08-05 10:12:45
* @LastEditors: wufan * @LastEditors: wufan
* @LastEditTime: 2021-07-05 10:23:10 * @LastEditTime: 2021-08-06 17:42:37
* @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);
} }
} }
...@@ -48,24 +48,24 @@ class GraphicsCourseList extends React.Component { ...@@ -48,24 +48,24 @@ class GraphicsCourseList extends React.Component {
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 = [
...@@ -76,7 +76,7 @@ class GraphicsCourseList extends React.Component { ...@@ -76,7 +76,7 @@ class GraphicsCourseList 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'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */} {/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
...@@ -92,7 +92,7 @@ class GraphicsCourseList extends React.Component { ...@@ -92,7 +92,7 @@ class GraphicsCourseList extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
...@@ -101,7 +101,7 @@ class GraphicsCourseList extends React.Component { ...@@ -101,7 +101,7 @@ class GraphicsCourseList extends React.Component {
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>;
}, },
}, },
{ {
...@@ -118,7 +118,7 @@ class GraphicsCourseList extends React.Component { ...@@ -118,7 +118,7 @@ class GraphicsCourseList extends React.Component {
</Tooltip> </Tooltip>
)} )}
</div> </div>
) );
}, },
}, },
{ {
...@@ -149,7 +149,7 @@ class GraphicsCourseList extends React.Component { ...@@ -149,7 +149,7 @@ class GraphicsCourseList 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)}
/> />
) );
}, },
}, },
{ {
...@@ -158,7 +158,7 @@ class GraphicsCourseList extends React.Component { ...@@ -158,7 +158,7 @@ class GraphicsCourseList extends React.Component {
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>;
}, },
}, },
{ {
...@@ -168,7 +168,7 @@ class GraphicsCourseList extends React.Component { ...@@ -168,7 +168,7 @@ class GraphicsCourseList 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);
}, },
}, },
{ {
...@@ -178,7 +178,7 @@ class GraphicsCourseList extends React.Component { ...@@ -178,7 +178,7 @@ class GraphicsCourseList 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);
}, },
}, },
{ {
...@@ -197,7 +197,7 @@ class GraphicsCourseList extends React.Component { ...@@ -197,7 +197,7 @@ class GraphicsCourseList extends React.Component {
<span key={item.planId}> <span key={item.planId}>
{item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '} {item.planName} {index < record.relatedPlanList.length - 1 && <span></span>}{' '}
</span> </span>
) );
})} })}
</Tooltip> </Tooltip>
</When> </When>
...@@ -206,7 +206,7 @@ class GraphicsCourseList extends React.Component { ...@@ -206,7 +206,7 @@ class GraphicsCourseList extends React.Component {
</Otherwise> </Otherwise>
</Choose> </Choose>
</div> </div>
) );
}, },
}, },
{ {
...@@ -235,42 +235,42 @@ class GraphicsCourseList extends React.Component { ...@@ -235,42 +235,42 @@ class GraphicsCourseList extends React.Component {
</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((item, index) => {
selectPlanList[item.planId] = {} selectPlanList[item.planId] = {};
selectPlanList[item.planId].planId = item.planId selectPlanList[item.planId].planId = item.planId;
selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }] selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }];
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(
...@@ -278,10 +278,10 @@ class GraphicsCourseList extends React.Component { ...@@ -278,10 +278,10 @@ class GraphicsCourseList extends React.Component {
RelatedPlanModalVisible: false, RelatedPlanModalVisible: false,
}, },
() => { () => {
this.props.onChange() this.props.onChange();
}
)
} }
);
};
renderMoreOperate = (item) => { renderMoreOperate = (item) => {
return ( return (
...@@ -291,16 +291,16 @@ class GraphicsCourseList extends React.Component { ...@@ -291,16 +291,16 @@ class GraphicsCourseList extends React.Component {
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>
...@@ -308,26 +308,26 @@ class GraphicsCourseList extends React.Component { ...@@ -308,26 +308,26 @@ class GraphicsCourseList extends React.Component {
删除 删除
</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({
...@@ -341,27 +341,27 @@ class GraphicsCourseList extends React.Component { ...@@ -341,27 +341,27 @@ class GraphicsCourseList 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 { 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
...@@ -372,82 +372,82 @@ class GraphicsCourseList extends React.Component { ...@@ -372,82 +372,82 @@ class GraphicsCourseList 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((item, index) => {
selectPlanList[item.planId] = {} selectPlanList[item.planId] = {};
selectPlanList[item.planId].planId = item.planId selectPlanList[item.planId].planId = item.planId;
selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }] selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }];
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 { RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state const { RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state;
const { dataSource = [], totalCount, query } = this.props const { dataSource = [], totalCount, query } = this.props;
const { current, size } = query const { current, size } = query;
return ( return (
<div className='video-course-list'> <div className='video-course-list'>
...@@ -472,8 +472,8 @@ class GraphicsCourseList extends React.Component { ...@@ -472,8 +472,8 @@ class GraphicsCourseList 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>
...@@ -500,8 +500,8 @@ class GraphicsCourseList extends React.Component { ...@@ -500,8 +500,8 @@ class GraphicsCourseList extends React.Component {
{this.state.shareLiveModal} {this.state.shareLiveModal}
{this.state.watchDataModal} {this.state.watchDataModal}
</div> </div>
) );
} }
} }
export default GraphicsCourseList export default GraphicsCourseList;
...@@ -3,7 +3,7 @@ import college from '@/common/lottie/college'; ...@@ -3,7 +3,7 @@ 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 PlanService from '@/domains/plan-domain/planService';
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';
...@@ -65,15 +65,33 @@ class RelatedPlanModal extends React.Component { ...@@ -65,15 +65,33 @@ 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) => {
return <span>{val}</span>; return <span>{val}
</span>;
}, },
}, },
]; ];
...@@ -159,7 +177,7 @@ class RelatedPlanModal extends React.Component { ...@@ -159,7 +177,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 +188,7 @@ class RelatedPlanModal extends React.Component { ...@@ -170,7 +188,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);
...@@ -216,8 +234,6 @@ class RelatedPlanModal extends React.Component { ...@@ -216,8 +234,6 @@ class RelatedPlanModal extends React.Component {
taskBaseVOList = selectPlan.taskBaseVOList; taskBaseVOList = selectPlan.taskBaseVOList;
} }
console.log('taskBaseVOList', taskBaseVOList);
return ( return (
<div> <div>
<XMTable <XMTable
......
.related-plan-modal{ .table-title-tooltip {
.search-container{ max-width: 600px!important;
margin-bottom:16px; }
} .related-plan-modal {
.select-container{ .table-title {
margin-bottom:12px; margin-left: 8px;
.con{ margin-left: 5px;
background: #E9EFFF; 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; border-radius: 4px;
padding:6px 16px; padding: 6px 16px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.tip{ .tip {
font-size:14px; font-size: 14px;
color:#2966FF; color: #2966ff;
margin-right:8px; margin-right: 8px;
} }
.text{ .text {
font-size:14px; font-size: 14px;
color:#666; color: #666;
margin-right:30px; margin-right: 30px;
} }
.clear{ .clear {
color:#2966FF; color: #2966ff;
font-size:14px; font-size: 14px;
} }
} }
} }
.plan-table{ .plan-table {
.taskName{ .taskName {
color:#666666; color: #666666;
font-size:14px; font-size: 14px;
} }
.task-learn-percentage{ .task-learn-percentage {
color:#666666; color: #666666;
font-size:14px; font-size: 14px;
} }
.course-info{ .course-info {
margin-left:57px; margin-left: 57px;
.course-type{ .course-type {
font-size:11px; font-size: 11px;
color:#666666; color: #666666;
padding:0px 6px; padding: 0px 6px;
line-height: 16px; line-height: 16px;
border: 1px solid #999999; border: 1px solid #999999;
margin-right:4px; margin-right: 4px;
border-radius: 2px; border-radius: 2px;
} }
.course-name{ .course-name {
color:#666666; color: #666666;
font-size:14px; font-size: 14px;
margin-right:8px; margin-right: 8px;
} }
.tip{ .tip {
font-size:14px; font-size: 14px;
color:#FF4F4F; color: #ff4f4f;
margin-right:2px; margin-right: 2px;
} }
.course-state{ .course-state {
color:#999; color: #999;
font-size:14px; font-size: 14px;
} }
} }
.ant-table-content{ .ant-table-content {
border:1px solid #e8e8e8; border: 1px solid #e8e8e8;
tr{ tr {
td{ td {
border:none; border: none;
} }
.child-table{ .child-table {
.ant-table-content{ .ant-table-content {
border:none; border: none;
thead{ thead {
display:none; display: none;
} }
tbody tr td{ tbody tr td {
border-bottom:none; border-bottom: none;
} }
} }
} }
} }
.odd-row{ .odd-row {
background:transparent; background: transparent;
td{ td {
background: #FFF; background: #fff;
} }
& + .ant-table-expanded-row{ & + .ant-table-expanded-row {
background:transparent; background: transparent;
td{ td {
background: #FFF; background: #fff;
} }
} }
&:hover{ &:hover {
& + .ant-table-expanded-row{ & + .ant-table-expanded-row {
background:transparent; background: transparent;
td{ td {
background: #F3f6fa !important; background: #f3f6fa !important;
} }
} }
} }
} }
.even-row{ .even-row {
background:transparent; background: transparent;
td{ td {
background: #FAFAFA; background: #fafafa;
} }
& + .ant-table-expanded-row{ & + .ant-table-expanded-row {
background:transparent; background: transparent;
td{ td {
background: #FAFAFA; background: #fafafa;
} }
} }
&:hover{ &:hover {
& + .ant-table-expanded-row{ & + .ant-table-expanded-row {
background:transparent; background: transparent;
td{ td {
background: #F3f6fa !important; background: #f3f6fa !important;
} }
} }
} }
......
...@@ -269,7 +269,7 @@ class VideoCourseList extends React.Component { ...@@ -269,7 +269,7 @@ 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'}>
......
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;
...@@ -278,18 +278,19 @@ ...@@ -278,18 +278,19 @@
} }
.circle-tip { .circle-tip {
position: absolute; position: absolute;
left: 70%;
padding-left: 16px; padding-left: 16px;
&.unfinished { &.unfinished {
top: 152px; bottom: 2%;
left: 37%;
.spot { .spot {
background: #2966ff; background: #2966ff;
} }
} }
&.finished { &.finished {
top: 232px; bottom: 2%;
left: 14%;
.spot { .spot {
background: #ffbb54; background: #14cca7;
} }
} }
.spot { .spot {
...@@ -297,7 +298,7 @@ ...@@ -297,7 +298,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 {
......
...@@ -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-09 15:53:08
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
......
...@@ -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)
} }
} }
......
...@@ -137,7 +137,7 @@ function Header(props) { ...@@ -137,7 +137,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>
...@@ -383,7 +383,7 @@ function Header(props) { ...@@ -383,7 +383,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>
......
...@@ -6,26 +6,15 @@ ...@@ -6,26 +6,15 @@
* @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,33 +33,24 @@ interface AddEmployeeModalProps { ...@@ -44,33 +33,24 @@ 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);
...@@ -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
style={{
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
overflow: 'hidden', overflow: 'hidden',
}}>{props.choosedItem.weChatAccount}</div> }}>
{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 }}
>
<div style={{
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
overflow: 'hidden', overflow: 'hidden',
}}> }}>
{props.choosedItem.depNameList.map((item:any,index:any)=>{ {props.choosedItem.depNameList.map((item: any, index: any) => {
return <span><WWOpenDataCom type="departmentName" openid={item}/>{index<props.choosedItem.depNameList.length-1?'、':''}</span> return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
{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 { withRouter } from "react-router-dom";
import { Tabs } from 'antd';
import Service from '@/common/js/service';
import Breadcrumbs from "@/components/Breadcrumbs";
import DataInfo from './components/DataInfo'
import CourseTable from './components/CourseTable';
import ExamTable from './components/ExamTable';
import StudyTable from './components/StudyTable';
import './index.less'
const { TabPane } = Tabs;
function DataCenter(props: any) {
const { match: { params: { taskId } } } = props;
const [info, setInfo] = useState<any>({})
const [tabKey, setTabKey] = useState<any>('')
const tabList = [
{
name: '课程目录',
key: 'course',
compoment: CourseTable,
},
{
name: '学习详情',
key: 'study',
compoment: StudyTable,
},
{
name: '考试详情',
key: 'exam',
compoment: ExamTable,
},
]
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 <TabPane tab={item.name} key={item.key}>
{
<item.compoment info={info} taskId={taskId} />
}
</TabPane>
})
}
</Tabs>
</div>
</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;
}
}
}
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 IndexText = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
const [list, setList] = useState<any[]>([]);
useEffect(() => {
console.log(props.info, 'rtyuio')
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">
<div className="title">失效课程</div>
<div className="list">
<ExpiredCourseList expiredCourseList={list} />
</div>
</div>
</div>
}
export default withRouter(CourseTable)
\ No newline at end of file
/*
* @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, { 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 /(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.cultureCustomerNum}`}</div>
<div className="item">{`完成人数:${props.info.finishCustomerNum}`}</div>
</div>
</div>
</div>
}
export default withRouter(DataInfo)
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { 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 [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.tatal)
})
}
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' onClick={() => { }}>
查看数据
</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">&#xe6b2;</span>
) : (
<span className="icon iconfont edit-icon">&#xe600;</span>
)}
</div>
<div className="stage-name">
{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';
import UserLearningData from '../UserLearningData';
import { timers } from 'jquery';
const { Search } = Input;
const { Option } = Select;
declare var formatDate: any;
function StudyTable(props: any) {
console.log(props, 'rtyjukl')
const [query, setQuery] = useState<any>({
current: 1, 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 timer = useRef<any>(null)
useEffect(() => {
getList()
}, [query])
function getList() {
Service.Hades('public/hades/getTaskCustomerRecordPage', query).then((res: any) => {
res.result.records.map((item: any) => {
item.department =item.departmentNameList.map((_item:any)=>{
if (_item == '1000') {
return '微信'
} else {
return <WWOpenDataCom type="departmentName" openid={_item} />
}
})
})
setList(res.result.records)
setTotal(res.result.total)
})
}
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: '学习进度',
key: 'learnPercentage',
dataIndex: 'learnPercentage',
render: (val: any, record: any) => {
return `${val}%`;
},
},
{
title: '最近学习时间',
key: 'latelyLearnTime',
dataIndex: 'latelyLearnTime',
sorter: true,
width: 240,
render: (val: any, record: any) => {
return `${formatDate('YYYY-MM-DD H:i', parseInt(record.latelyLearnTime))}`;
},
},
{
title: '开始学习时间',
key: 'startLearnTime',
dataIndex: 'startLearnTime',
width: 240,
sorter: true,
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>
);
},
},
];
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,
current: 1,
customerName: name
})
}, 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,
current: 1,
learnState: value
})
}}
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,
current: 1,
startTime: dates[0]?.startOf('day').valueOf(),
endTime: dates[0]?.endOf('day').valueOf(),
})
}}
/>
</div>
</div>
</Col>
<Col offset={2} span={1} >
<Tooltip title='清空筛选'>
<span className='resetBtn iconfont icon' onClick={() => {
setName('');
setQuery({
current: 1, size: 10,
taskId: props.taskId,
source: 0,
storeId: User.getStoreId(),
storeUserId: User.getStoreUserId(),
userId: User.getStoreUserId(),
})
}}>
&#xe61b;{' '}
</span>
</Tooltip>
</Col>
</Row>
</div>
<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 && 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>
<Route path={`${props.match.url}/user-learning-data/:storeCustomerId`} render={() => <UserLearningData taskId={props.taskId} />} />
</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);
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-05 11:26:25
* @LastEditors: yuananting
* @LastEditTime: 2021-08-13 19:17:49
* @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}/{item.learnAllNum}
</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}
/>
)}
<span className='text'>{learnFinishPercentage === 100 ? '已完成' : `${learnFinishPercentage}%`}</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 32px;
border-bottom: 1px dashed #E8E8E8;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
&.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;
}
}
}
}
}
.study_Table{
}
\ No newline at end of file
.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{
width: 42px;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #999999;
line-height: 20px;
text-align: center;
margin-left: 24px;
}
}
.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;
.wcl{
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #666666;
line-height: 22px;
text-align: center;
}
}
.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-13 19:37:07
* @Description: 任务中心-培训任务-新建页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState, useRef } from 'react';
import { Button, message } 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';
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const DEFAULT_BASIC_INFO = {
taskName: '', // 培训任务名称
coverUrl: defaultCover,
coverId: null,
helpStoreUserIds: [], // 指定协同者id
timeType: 'FOREVER', // 培训时间,默认永久有效
startTime: null, // 固定时间段-开始时间
endTime: null, // 固定时间段-结束时间
learnType: 'FREEDOM', // 学习模式,默认自由学习
assignList: [], // 指派列表-assignId assignType
introduce: null, // 培训目的
loadintroduce: false,
};
const DEFAULT_STAGE_LIST = [
{
stageName: '阶段一',
contentList: [],
check: false,
},
];
const DEFAULT_FINISH_STANDARD = {
percentCompleteLive: 80,
percentCompleteVideo: 80, // 线上课完成百分比
percentCompletePicture: 80, // 图文课完成百分比
};
function AddTrainTask() {
const type = getParameterByName('type');
const taskId = getParameterByName('taskId');
const [activeStep, setActiveStep] = useState('BASIC_INFO');
const [basicInfo, setBasicInfo] = useState(DEFAULT_BASIC_INFO);
const [stageList, setStageList] = useState(DEFAULT_STAGE_LIST);
const [finishStandard, setFinishStandard] = useState(DEFAULT_FINISH_STANDARD); // 完成百分比
const [startCheck, setStartCheck] = useState(false); // 是否启动校验
const basicInfoRef = useRef(null);
useEffect(() => {
basicInfoRef.current = basicInfo;
}, [basicInfo]);
useEffect(() => {
if (type === 'edit') {
initTaskData();
}
}, []);
// 编辑回显数据详情
function initTaskData() {
TaskCenterService.getTrainingTaskDetail({
taskId,
}).then((res) => {
const {
result: {
createId,
taskName,
courseMediaVOS,
helpStoreUsers,
timeType,
startTime,
endTime,
learnType,
assignList,
percentCompleteLive,
percentCompletePicture,
percentCompleteVideo,
trainingStageList,
},
} = res;
const _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,
endTime,
learnType,
};
const _stageList = trainingStageList.map((item) => {
item.contentList = item.contentVOList;
item.type = 'text';
return item;
});
const introduceInfo = courseMediaVOS.filter((item) => item.contentType === 'INTRO')[0];
setBasicInfo({ ...basicInfoRef.current, ...ITEM_BASIC_INFO, loadintroduce: !introduceInfo });
setFinishStandard({
percentCompleteLive,
percentCompletePicture,
percentCompleteVideo,
});
setStageList(_stageList);
introduceInfo && getTextDetail('introduce', introduceInfo.mediaUrl);
});
}
// 获取培训目的内容
function getTextDetail(key, url) {
$.ajax({
data: {},
type: 'GET',
url,
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
success: (res) => {
setBasicInfo({
...basicInfoRef.current,
[key]: res,
[`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>
{/* disabled={submitDisabled} */}
</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>
{/* disabled={submitDisabled} */}
</div>
</Otherwise>
</Choose>
);
}
// 确认保存新建
function submitRemote(introduceId, issue) {
const { assignList, endTime, helpStoreUserIds, learnType, startTime, taskName, timeType, coverId } = basicInfo;
const { percentCompleteLive, percentCompletePicture, percentCompleteVideo } = finishStandard;
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.assignType,
};
});
const commonParams = {
assignList: _assignList,
createId: User.getStoreUserId(),
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 } = basicInfo;
const { percentCompleteLive, percentCompleteVideo, percentCompletePicture } = finishStandard;
if (!taskName) {
return message.warning('请输入培训任务名称');
}
if (assignList.length === 0) {
return message.warning('请选择指派对象');
}
if (stageList.length === 0) {
return message.warning('请添加阶段');
}
const stageNameEmpty = stageList.filter((item) => !item.stageName);
if (stageNameEmpty.length > 0) {
return message.warning('请输入阶段名称');
}
const stageNameArr = stageList.map((item) => item.stageName);
const stageNameSet = new Set(stageNameArr);
if (stageNameSet.size !== stageNameArr.length) {
return message.warning('阶段名称不能重复');
}
if (percentCompleteLive === '' || percentCompleteVideo === '' || percentCompletePicture === '') {
return message.warning('请输入完成标准');
}
if (basicInfo.introduce) {
Upload.uploadTextToOSS(
basicInfo.introduce,
`${randomString()}.txt`,
(introduceId) => {
submitRemote(introduceId, issue);
},
() => message.warning('上传培训目的失败')
);
} else {
submitRemote(null, issue);
}
}
// 返回、取消
function handleGoBack() {
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 if (field === 'timeType' && value === 'FOREVER') {
setBasicInfo({
...basicInfoRef.current,
[field]: value,
startTime: null,
endTime: null,
});
} else {
setBasicInfo({
...basicInfoRef.current,
[field]: value,
});
}
}
function handleChangeStageInfo(field, value) {
if (field === 'stageList') {
setStageList(value);
} else {
setFinishStandard(value);
}
}
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} finishStandard={finishStandard} 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-10 15:10:44
* @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: {}, // 排序
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', getTrainingTaskPage);
Bus.bind('getStoreTaskNum', getStoreTaskNum);
};
const removePageData = () => {
Bus.unbind('getTrainingTaskPage', getTrainingTaskPage);
Bus.unbind('getStoreTaskNum', getStoreTaskNum);
};
// 获取计划列表
function getTrainingTaskPage() {
let _query = _.clone(query);
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-13 19:38:30
* @Description: 任务中心-培训任务-新建-基本信息
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { 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';
import User from '@/common/js/user';
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
function BasicInfo(props) {
const taskState = getParameterByName('taskState');
const type = getParameterByName('type');
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) {
setImgClipVisible(true);
setImageFile(file);
}
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);
props.onClose();
}
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);
}
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={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 && !startTime && !endTime ? 'error' : ''}
help={startCheck && !startTime && !endTime && '请选择培训时间'}>
<RangePicker
style={{ width: 320 }}
showTime={{ defaultValue: [moment().add(10, 'minutes'), moment().add(10, 'minutes')] }}
ranges={{
7: [moment().add(10, 'minute'), moment().add(6, 'day').endOf('day')],
1个月: [moment().add(10, 'minute'), moment().add(1, 'month').endOf('day')],
3个月: [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;
}
}
.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: 12px;
font-size: 14px;
}
}
}
/*
* @Author: yuananting
* @Date: 2021-08-03 10:47:59
* @LastEditors: yuananting
* @LastEditTime: 2021-08-03 18:31:56
* @Description: 编辑培训任务-失效课程
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { List } from 'antd';
import './ExpiredCourseList.less';
import ENUM from '../../enum';
function ExpiredCourseList(props) {
const expiredCourseList = [1, 2, 3, 4];
return (
<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'>阶段一、</span>
<span className='content-name'>
<img src={ENUM.LearningContentIcon['LIVE']} />
<span>2.1 入门培训任务</span>
</span>
<span className='teacher-name'>张老师</span>
<span className='split'>|</span>
<span className='course-time'>2020-12-12 09:00~10:00</span>
<span className='del-btn'>删除记录</span>
</div>
</List.Item>
)}
/>
</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-11 11:27:09
* @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 './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];
console.log('data=========>', _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];
let currentVideoCourseListData = [];
_data.map((item, index) => {
item.contentList.map((childItem, childIndex) => {
if (childItem.courseType === 'VOICE') {
currentVideoCourseListData.push(childItem.courseId);
}
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.courseId);
}
return childItem;
});
return item;
});
const params = {
...pictureQuery,
size: pictureSize,
courseType: 'PICTURE',
storeId: User.getStoreId(),
excludeCourseIdList: currentPictureCourseListData,
};
Service.Hades('public/hades/mediaCoursePage', params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
pictureDataSource: records,
pictureTotalCount: Number(total),
currentPictureCourseListData,
});
});
};
handleChangVideoCourseName = (value) => {
const { videoQuery, 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: '40%',
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>
<div className='course-name'>{record.courseName}</div>
{/* <span
className='course-status'
style={{ color: courseStateShow[record.courseState].color, border: `1px solid ${courseStateShow[record.courseState].color}` }}>
{courseStateShow[record.courseState].title}
</span> */}
</div>
</div>
);
} 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: 'courseTime',
dataIndex: 'courseTime',
width: '40%',
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: #2966ff;
font-size: 14px;
}
}
.related-box {
padding: 6px 16px;
background: #e9efff;
border-radius: 4px;
flex: 1;
color: #666666;
font-size: 14px;
}
}
.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: 180px;
}
.course-status {
font-size: 12px;
line-height: 18px;
display: inline-block;
border-radius: 2px;
padding: 0 8px;
margin-top: 8px;
}
}
.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-11 11:49:11
* @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,
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) => {
const contentList = props.stageList.map((item) => {
return item.contentList;
});
const existedPaperId = contentList.flat().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-11 13:33:44
* @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 = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
// const id = window.getParameterByName('id');
// const type = window.getParameterByName('type');
class TrainContent extends Component {
constructor(props) {
super(props);
this.state = {
basicInfo: props.basicInfo,
stageList: props.stageList,
finishStandard: props.finishStandard,
showCourseDrawer: false,
showExamDrawer: false,
selectedStageIndex: 0,
expiredCourseList: [], // 失效课程
showStandardDetail: false, // 是否展开高级设置
};
}
componentDidMount() {}
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 ghost>
<Panel header={this.renderStageInfo(item, index)} key={index}>
<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) => {
const { finishStandard } = this.state;
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(
{
finishStandard: {
...finishStandard,
[field]: _percentComplete,
},
},
() => {
this.props.onChange('finishStandard', this.state.finishStandard);
}
);
};
render() {
const { stageList, showCourseDrawer, showExamDrawer, expiredCourseList, showStandardDetail, finishStandard, basicInfo, selectedStageIndex } = this.state;
console.log('finishStandard==================>', finishStandard);
const { percentCompleteLive, percentCompleteVideo, percentCompletePicture } = finishStandard;
const { startCheck } = this.props;
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>
{window.getParameterByName('type') === 'edit' && expiredCourseList.length > 0 && (
<div className='expired-info__wrap'>
<div className='module-title'>失效课程</div>
<ExpiredCourseList expiredCourseList={expiredCourseList} />
</div>
)}
<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;
.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 && value[0].valueOf();
_query.endTime = value && value[1].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-13 18:30:47
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState } from 'react';
import { Route, 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, issueState) {
Modal.confirm({
title: '提示',
content:
issueState === 'YES'
? '发布后,被指派学员将任务列表中看到该任务,确定要发布?'
: '取消发布后,任务对学员暂不可见,可能会影响正在学习学员,确定要取消?',
okText: '确定',
cancelText: '取消',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
TaskCenterService.updateIssueStateTraining({
taskId,
issueState,
}).then((res) => {
message.success(issueState === 'YES' ? '发布成功' : '取消发布成功');
Bus.trigger('getTrainingTaskPage');
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');
Bus.trigger('getStoreTaskNum');
});
},
});
}
// 编辑培训任务-跳转新建/编辑页
function editTrainingTask(item) {
window.RCHistory.push({
pathname: `/create-train-task?type=edit&taskId=${item.taskId}&taskState=${item.taskState}`,
});
}
// 显示指派弹窗
function handleShowAssignModal(item) {
const 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');
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='live-course-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: '10%',
key: 'cultureCustomerNum',
dataIndex: 'cultureCustomerNum',
sorter: true,
render: (val, record) => {
return (
<Tooltip
title={
<div>
<div>未完成:{record.startingCustomerNum}</div>
<div>已完成:{record.finishCustomerNum}</div>
<div>已逾期:{record.overdueCustomerNum}</div>
</div>
}>
<span style={{ color: '#2966FF' }}>{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: '10%',
key: 'finishPercent',
dataIndex: 'finishPercent',
sorter: true,
render: (val) => {
return <span>{val}%</span>;
},
},
{
title: '培训时间',
width: '12.5%',
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: '12%',
key: 'created',
dataIndex: 'created',
sorter: true,
render: (val) => {
return <span style={{ whiteSpace: 'nowrap' }}>{window.formatDate('YYYY-MM-DD H:i', val)}</span>;
},
},
{
title: '更新时间',
width: '12%',
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: '16%',
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,
};
props.onChange(_query);
}
function handleChangeTable(pagination, filters, sorter) {
const { columnKey, order } = sorter;
const { query } = props;
let _columnKey;
let _order;
if (columnKey == 'cultureCustomerNum' && order === 'ascend') {
// 按学习人数升序排序
_columnKey = 'CUSTOMER_NUM';
_order = 'SORT_ASC';
}
if (columnKey == 'cultureCustomerNum' && order === 'descend') {
// 按学习人数降序排序
_columnKey = 'CUSTOMER_NUM';
_order = 'SORT_DESC';
}
if (columnKey == 'finishPercent' && order === 'ascend') {
// 按完成率升序排序
_columnKey = 'FINISH_PERCENT';
_order = 'SORT_ASC';
}
if (columnKey == 'finishPercent' && order === 'descend') {
// 按完成率降序排序
_columnKey = 'FINISH_PERCENT';
_order = 'SORT_DESC';
}
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: {},
};
_query.sortMap[_columnKey] = _order;
props.onChange(_query);
}
function handleCreatePlan() {
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={handleCreatePlan}>
新建培训任务
</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: 1600 }}
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) => {
handleChangeQuery('current', page + 1);
}}
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;
.train-cover {
width: 106px;
height: 60px;
border-radius: 2px;
margin-right: 8px;
}
.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-text {
color: #2966ff;
cursor: pointer;
}
}
.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;
}
}
}
}
}
}
.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-13 19:18:54
* @Description: 新建培训任务-选择指派对象
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { Modal, Tree, Tooltip, AutoComplete, Tabs } 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 './ChooseAssignorModal.less';
const { TabPane } = Tabs;
const { DirectoryTree } = Tree;
function ChooseAssignorModal(props) {
const [structureData, setStructureData] = useState([]);
const [activeKey, setActiveKey] = useState('departMentTab');
const [checkedAssignorList, setCheckedAssignorList] = useState(props.currentAssignorList || []); // 勾选的指派对象
const [checkedAssignorKeys, setCheckedAssignorKeys] = useState(props.currentAssignorList.map((item) => item.checkedId) || []);
const [completeOptions, setCompleteOption] = useState([]);
const [searchKey, setSearchKey] = useState('');
const [open, setOpen] = useState(false);
const AssignTypeEnum = {
departMentTab: 'SECTION',
postGrouptab: 'POST',
customGroupTab: 'CUSTOM',
};
useEffect(() => {
getStructureData();
}, [activeKey]);
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 = checkedAssignorList.map((item) => item.checkedId);
setStructureData(structureData);
setCheckedAssignorKeys(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.checkedId;
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 = _checkedAssignorList.map((item) => item.checkedId);
setCheckedAssignorKeys(_checkedAssignorKeys);
setCheckedAssignorList(_checkedAssignorList);
}
function removeCheckedAssignor(item) {
const _checkedAssignorList = checkedAssignorList.filter((childItem) => childItem.checkedId !== item.checkedId);
const _checkedAssignorKeys = _checkedAssignorList.map((item) => item.checkedId);
setCheckedAssignorKeys(_checkedAssignorKeys);
setCheckedAssignorList(_checkedAssignorList);
}
function clearCheckedAssignor() {
setCheckedAssignorKeys([]);
setCheckedAssignorList([]);
}
function renderTitle(title) {
return <span>{title}</span>;
}
function renderItem(record, type) {
return {
value: record.userName || record.name,
label: (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
depId={record.id}
type={type}>
{type === 'user' ? (
<div>
<WWOpenDataCom type='userName' openid={record.userName} />
</div>
) : (
<div>
<WWOpenDataCom type='departmentName' openid={record.name} />
</div>
)}
{type === 'user' &&
record.postDepNamesList.map((item, index) => {
return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
</span>
);
})}
{type === 'post' && (
<span type='post' openid={record.parentName}>
{record.parentName}
</span>
)}
</div>
),
};
}
function getCompleteOptionData(value) {
setCompleteOption([]);
const params = {
depType: DepType[props.treeType],
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 _completeOptions = [];
const userObj = {};
const departmentGroupObj = {};
const postobj = {};
const { result = {} } = res;
const { departmentUserVOList = [], departmentVOList = [], subLevelDepartmentVOList = [] } = result;
if (departmentUserVOList.length > 0) {
userObj.label = renderTitle('学员');
userObj.options = departmentUserVOList.map((item, index) => {
return renderItem(item, 'user');
});
}
if (departmentVOList.length > 0) {
switch (props.treeType) {
case 'departMentTab':
departmentGroupObj.label = renderTitle('部门');
break;
case 'postGrouptab':
departmentGroupObj.label = renderTitle('岗位组');
break;
case 'customGroupTab':
departmentGroupObj.label = renderTitle('分组集合');
break;
default:
break;
}
departmentGroupObj.options = departmentVOList.map((item, index) => {
return renderItem(item, 'group');
});
}
if (subLevelDepartmentVOList.length > 0) {
switch (props.treeType) {
case 'postGrouptab':
postobj.label = renderTitle('岗位');
break;
case 'customGroupTab':
postobj.label = renderTitle('分组');
break;
default:
break;
}
postobj.options = subLevelDepartmentVOList.map((item, index) => {
return renderItem(item, 'post');
});
}
if (Object.keys(userObj).length !== 0) {
_completeOptions.push(userObj);
}
if (Object.keys(departmentGroupObj).length !== 0) {
_completeOptions.push(departmentGroupObj);
}
if (Object.keys(postobj).length !== 0) {
_completeOptions.push(postobj);
}
setCompleteOption(_completeOptions);
});
}
function handlePlaceHolder() {
let placeholder = '';
switch (props.treeType) {
case 'departMentTab':
placeholder = '搜索学员姓名、部门';
break;
case 'postGrouptab':
placeholder = '搜索学员姓名/岗位/岗位组';
break;
case 'customGroupTab':
placeholder = '搜索学员姓名/自定义分组集合/自定义分组';
break;
default:
break;
}
return placeholder;
}
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'>
{/* <AutoComplete
dropdownClassName='certain-category-search-dropdown'
dropdownMatchSelectWidth={272}
allowClear
onChange={(value) => setSearchKey(value)}
onSearch={(value) => {
getCompleteOptionData(value);
}}
// notFoundContent={notFoundContentNode()}
value={searchKey}
open={open}
onFocus={() => {
setOpen(true);
}}
onBlur={() => {
setOpen(false);
}}
style={{
width: 272,
}}
options={completeOptions}
// onSelect={confirmSearchSelect}
placeholder={handlePlaceHolder()}></AutoComplete> */}
<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'>
<DirectoryTree
defaultExpandAll
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>
);
}}
/>
</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'>
{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>
);
})}
</div>
</div>
</div>
</Modal>
);
}
export default ChooseAssignorModal;
.choose-assignor-modal {
.assignor-container {
display: flex;
height: 417px;
.left-list {
width: 50%;
margin-right: 24px;
padding: 0 0 12px 16px;
border: 1px solid #e8e8e8;
.data-body {
.ant-tabs-nav {
margin: 0 !important;
.ant-tabs-tab {
padding: 16px 0 10px 0 !important;
font-size: 14px !important;
}
}
.tree-con {
overflow-y: scroll;
overflow-x: hidden;
max-height: 367px;
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;
.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-13 19:39:34
* @Description: 新建培训任务-选择协同人员
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { Modal, Input, Tooltip, List, Checkbox } from 'antd';
import { XMTable } from '@/components';
import college from '@/common/lottie/college.json';
import Service from '@/common/js/service';
import User from '@/common/js/user';
import StoreService from '@/domains/store-domain/storeService';
import search from '@/common/lottie/search';
import './ChooseCollaboratorModal.less';
import WWOpenDataCom from '@/components/WWOpenDataCom';
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'>
<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>
)}
/>
</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;
padding-right: 16px;
.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;
}
}
}
}
}
...@@ -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,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 {
......
...@@ -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