Commit 5314960e by yuananting

fix:解决合并代码的冲突

parents ce5b164f 571796c7
@font-face {
font-family: 'iconfont'; /* Project id 2223403 */
src: url('//at.alicdn.com/t/font_2223403_2digpsfgq8l.woff2?t=1628853864698') format('woff2'),
url('//at.alicdn.com/t/font_2223403_2digpsfgq8l.woff?t=1628853864698') format('woff'),
url('//at.alicdn.com/t/font_2223403_2digpsfgq8l.ttf?t=1628853864698') format('truetype');
src: url('//at.alicdn.com/t/font_2223403_0b87tvtysw45.woff2?t=1629025918841') format('woff2'),
url('//at.alicdn.com/t/font_2223403_0b87tvtysw45.woff?t=1629025918841') format('woff'),
url('//at.alicdn.com/t/font_2223403_0b87tvtysw45.ttf?t=1629025918841') format('truetype');
}
.iconfont {
font-family: 'iconfont' !important;
......
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-08-09 15:52:49
* @LastEditors: wufan
* @LastEditTime: 2021-08-16 16:39:13
* @LastEditors: yuananting
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
*/
import { MapInterface } from '@/domains/basic-domain/interface'
import { path, live } from '@/domains/brand/constants'
import { MapInterface } from '@/domains/basic-domain/interface';
import { path, live } from '@/domains/brand/constants';
// 默认是 dev 环境
const ENV: string = process.env.DEPLOY_ENV || 'dev'
console.log('process.env.DEPLOY_ENV', process.env, ENV, 'hjkkkk')
const ENV: string = process.env.DEPLOY_ENV || 'dev';
console.log('process.env.DEPLOY_ENV', process.env, ENV, 'hjkkkk');
console.log('process.env.DEPLOY_ENV', process);
const BASIC_HOST_MAP: MapInterface = {
dev: 'https://dev-heimdall.xiaomai5.com/',
dev1: 'https://dev-heimdall.xiaomai5.com/',
rc: 'https://rc-heimdall.xiaomai5.com/',
gray: 'https://gray-heimdall.xiaomai5.com/',
prod: 'https://gateway.xiaomai5.com/',
}
};
const PATH_MAP: MapInterface = {
dev: 'https://dev.xiaomai5.com/xiaomai-cloud-class-web/h5.html',
dev1: 'https://dev.xiaomai5.com/dev1/xiaomai-cloud-class-web/h5.html',
rc: 'https://rc.xiaomai5.com/xiaomai-cloud-class-web/h5.html',
gray: path + '/gray/h5.html',
prod: path + '/h5.html',
}
};
export const YZ_APPId = "yozoqvpO2Hvz8346";
export const YZ_PREVIEW_URL: string = 'http://eic.yozocloud.cn/api/view/file'
export const OFFICE_PREVIEW_URL: string = 'https://view.officeapps.live.com/op/view.aspx'
export const YZ_APPId = 'yozoqvpO2Hvz8346';
export const YZ_PREVIEW_URL: string = 'http://eic.yozocloud.cn/api/view/file';
export const OFFICE_PREVIEW_URL: string = 'https://view.officeapps.live.com/op/view.aspx';
// axios headers config
export const TIME_OUT: number = 20000
export const USER_TYPE: string = 'B'
export const PROJECT = 'xmzj-web-b'
export const VERSION = '5.4.8'
export const PREFIX = 'cloud-class'
export const USER_PREFIX = 'store-live'
export const TIME_OUT: number = 20000;
export const USER_TYPE: string = 'B';
export const PROJECT = 'xmzj-web-b';
export const VERSION = '5.4.8';
export const PREFIX = 'cloud-class';
export const USER_PREFIX = 'store-live';
// host
export const BASIC_HOST: string = BASIC_HOST_MAP[ENV]
export const PATH: string = PATH_MAP[ENV]
export const BASIC_HOST: string = BASIC_HOST_MAP[ENV];
export const PATH: string = PATH_MAP[ENV];
/*
* @Author: 吴文洁
* @Date: 2020-08-20 09:21:40
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-08-11 22:50:48
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 16:38:55
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import { MapInterface } from '@/domains/basic-domain/interface'
import { path, live } from '@/domains/brand/constants'
import { MapInterface } from '@/domains/basic-domain/interface';
import { path, live } from '@/domains/brand/constants';
const ENV: string = process.env.DEPLOY_ENV || 'dev';
const appIdMap: MapInterface = {
dev: 'wx3ea60e78ddfa277e',
dev1: 'wx3ea60e78ddfa277e',
rc: 'wx5c5a1fb71ecab7bc',
gray: "wx3dda02036493ada6", // 小麦校讯通
gray: 'wx3dda02036493ada6', // 小麦校讯通
prod: 'wx3dda02036493ada6',
}
};
const shareUrlMap: MapInterface = {
'dev': 'https://dev.xiaomai5.com/share/show?appid=',
'dev1': 'https://dev.xiaomai5.com/share/show?appid=',
'rc': 'https://rc.xiaomai5.com/share/show?appid=',
'prod': 'https://prod.xiaomai5.com/share/show?appid=',
'gray': 'https://prod.xiaomai5.com/share/show?appid=',
}
dev: 'https://dev.xiaomai5.com/share/show?appid=',
dev1: 'https://dev.xiaomai5.com/share/show?appid=',
rc: 'https://rc.xiaomai5.com/share/show?appid=',
prod: 'https://prod.xiaomai5.com/share/show?appid=',
gray: 'https://prod.xiaomai5.com/share/show?appid=',
};
const LIVE_SHARE_MAP: MapInterface = {
dev: 'https://dev.xiaomai5.com/store-live/index.html#/',
dev1: 'https://dev.xiaomai5.com/dev1/store-live/index.html#/',
rc: 'https://rc.xiaomai5.com/store-live/index.html#/',
gray: live+'/gray/index.html#/',
prod: live+'/index.html#/',
}
gray: live + '/gray/index.html#/',
prod: live + '/index.html#/',
};
export const appId: string = appIdMap[ENV];
export const shareUrl: string = shareUrlMap[ENV];
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: wufan
* @LastEditTime: 2021-08-13 19:25:12
* @LastEditTime: 2021-08-15 19:12:48
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_2digpsfgq8l.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_0b87tvtysw45.css">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: wufan
* @LastEditTime: 2021-08-13 19:25:18
* @LastEditTime: 2021-08-15 19:12:55
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......@@ -30,7 +30,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_2digpsfgq8l.css" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_0b87tvtysw45.css" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
......
......@@ -3,198 +3,199 @@
* @Author: zangsuyun
* @Date: 2021-03-12 14:49:40
* @LastEditors: yuananting
* @LastEditTime: 2021-07-18 16:58:23
* @LastEditTime: 2021-08-16 16:38:44
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Modal, message, Tooltip, Switch, Dropdown, Button } from "antd";
import React from 'react';
import { Modal, message, Tooltip, Switch, Dropdown, Button } from 'antd';
import { FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum';
import { Route, withRouter } from "react-router-dom";
import { PageControl, XMTable } from "@/components";
import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass";
import { appId, shareUrl, LIVE_SHARE } from "@/domains/course-domain/constants";
import ScanFileModal from "../../resource-disk/modal/ScanFileModal";
import WatchData from "./WatchData";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import ENUM from "../ENUM.js";
import "./KnowledgeBaseList.less";
import { Route, withRouter } from 'react-router-dom';
import { PageControl, XMTable } from '@/components';
import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import ScanFileModal from '../../resource-disk/modal/ScanFileModal';
import WatchData from './WatchData';
import KnowledgeAPI from '@/data-source/knowledge/request-api';
import ENUM from '../ENUM.js';
import './KnowledgeBaseList.less';
const DEFAULT_SIZE_UNIT = 1000 * 1000 // 将B转换成M
const { confirm } = Modal
const DEFAULT_SIZE_UNIT = 1000 * 1000; // 将B转换成M
const { confirm } = Modal;
const ENV = process.env.DEPLOY_ENV || 'dev';
class KnowledgeBaseList extends React.Component {
constructor(props) {
super(props)
super(props);
this.state = {
id: "",
scanFileModal: null
}
id: '',
scanFileModal: null,
};
}
handleAdminName = (adminArray) => {
let adminStr = ""
let adminStr = '';
adminArray.map((item, index) => {
if (index < adminArray.length - 1) {
adminStr = adminStr + item.adminName + "、"
adminStr = adminStr + item.adminName + '、';
} else {
adminStr = adminStr + item.adminName
}
})
return adminStr
adminStr = adminStr + item.adminName;
}
});
return adminStr;
};
handleUp = (index, record) => {
if (index === 0 && this.props.query.current === 1) {
return
return;
}
const params = {
direction: "UP",
direction: 'UP',
id: record.id,
storeId: record.storeId
}
storeId: record.storeId,
};
KnowledgeAPI.moveKnowledge(params).then((res) => {
if (res.success) {
message.success("更新成功")
this.props.onChange()
}
})
message.success('更新成功');
this.props.onChange();
}
});
};
handleDown = (record, index) => {
const { query, totalCount } = this.props
const { current, size } = query
const { query, totalCount } = this.props;
const { current, size } = query;
if (totalCount === size * (current - 1) + index + 1) {
return
return;
}
const params = {
direction: "DOWN",
direction: 'DOWN',
id: record.id,
storeId: record.storeId
}
storeId: record.storeId,
};
KnowledgeAPI.moveKnowledge(params).then((res) => {
if (res.success) {
message.success("更新成功")
this.props.onChange()
}
})
message.success('更新成功');
this.props.onChange();
}
});
};
getBlob = (url) => {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest()
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true)
xhr.responseType = "blob"
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
}
}
xhr.send()
})
resolve(xhr.response);
}
};
xhr.send();
});
};
saveAs = (blob, filename) => {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename)
navigator.msSaveBlob(blob, filename);
} else {
const link = document.createElement("a")
const body = document.querySelector("body")
const link = document.createElement('a');
const body = document.querySelector('body');
// 创建对象url
link.href = window.URL.createObjectURL(blob)
link.download = filename
link.href = window.URL.createObjectURL(blob);
link.download = filename;
body.appendChild(link)
body.appendChild(link);
link.click()
body.removeChild(link)
link.click();
body.removeChild(link);
// 通过调用 URL.createObjectURL() 创建的 URL 对象
window.URL.revokeObjectURL(link.href)
}
window.URL.revokeObjectURL(link.href);
}
};
// 下载文件
handleDownload = (folder) => {
this.getBlob(folder.ossUrl).then((blob) => {
this.saveAs(blob, folder.folderName)
})
}
this.saveAs(blob, folder.folderName);
});
};
// 预览文件
handleScanFile = (folder) => {
const { folderFormat, folderSize, ossUrl } = folder;
switch (folderFormat) {
case "PDF":
window.open(ossUrl, "_blank")
break
case "WORD":
case "DOCX":
case "DOC":
case "EXCEL":
case "PPT":
case "PPTX":
case "PDF":
case 'PDF':
window.open(ossUrl, '_blank');
break;
case 'WORD':
case 'DOCX':
case 'DOC':
case 'EXCEL':
case 'PPT':
case 'PPTX':
case 'PDF':
if (
((folderFormat === "PPT" || folderFormat === "PPTX" || folderFormat === "DOCX" || folderFormat === "WORD" || folderFormat === "DOC") &&
((folderFormat === 'PPT' || folderFormat === 'PPTX' || folderFormat === 'DOCX' || folderFormat === 'WORD' || folderFormat === 'DOC') &&
folderSize > 10 * DEFAULT_SIZE_UNIT) ||
(folderFormat === "EXCEL" && folderSize > 5 * DEFAULT_SIZE_UNIT)
(folderFormat === 'EXCEL' && folderSize > 5 * DEFAULT_SIZE_UNIT)
) {
Modal.confirm({
title: "抱歉,不能在线预览",
content: "由于文件较大,不支持在线预览,请下载后再查看",
title: '抱歉,不能在线预览',
content: '由于文件较大,不支持在线预览,请下载后再查看',
// icon: <Icon type="question-circle" theme="filled" style={{ color: '#FF8534' }}></Icon>,
cancelText: "取消",
okText: "下载",
cancelText: '取消',
okText: '下载',
onOk: () => {
this.handleDownload(folder)
}
})
break
this.handleDownload(folder);
},
});
break;
}
const prefixUrl = "https://view.officeapps.live.com/op/view.aspx?src="
const scanUrl = `${prefixUrl}${encodeURIComponent(ossUrl)}`
window.open(scanUrl, "_blank")
break
const prefixUrl = 'https://view.officeapps.live.com/op/view.aspx?src=';
const scanUrl = `${prefixUrl}${encodeURIComponent(ossUrl)}`;
window.open(scanUrl, '_blank');
break;
default:
const scanFileModal = (
<ScanFileModal
fileType={folderFormat}
item={folder}
close={() => {
this.setState({ scanFileModal: null })
this.setState({ scanFileModal: null });
}}
/>
)
this.setState({ scanFileModal })
break
}
);
this.setState({ scanFileModal });
break;
}
};
// 请求表头
parseColumns = () => {
const { query, totalCount } = this.props
const { current, size } = query
const { query, totalCount } = this.props;
const { current, size } = query;
const columns = [
{
title: "名称",
key: "name",
dataIndex: "name",
title: '名称',
key: 'name',
dataIndex: 'name',
render: (val, record) => {
const { coverUrl, mediaCourseUrl, courseDivision } = record.source
let hasCover = false
const type = record.type
const { coverUrl, mediaCourseUrl, courseDivision } = record.source;
let hasCover = false;
const type = record.type;
return (
<div
className='record__item'
onClick={() => {
this.handleScanFile(record.source)
this.handleScanFile(record.source);
}}>
<img src={FileTypeIcon[record.source.folderFormat] } style={{width:24,height:24}} alt='' className='item-img' />
<img src={FileTypeIcon[record.source.folderFormat]} style={{ width: 24, height: 24 }} alt='' className='item-img' />
{val.length > 25 ? (
<Tooltip title={val}>
<div className='course-name clamp'>{val}</div>
......@@ -203,15 +204,14 @@ class KnowledgeBaseList extends React.Component {
<div className='course-name clamp'>{val}</div>
)}
</div>
)
}
);
},
},
{
title: "创建人",
key: "createName",
dataIndex: "createName",
align: "center",
title: '创建人',
key: 'createName',
dataIndex: 'createName',
align: 'center',
render: (val) => {
return (
<div>
......@@ -221,36 +221,35 @@ class KnowledgeBaseList extends React.Component {
</Tooltip>
)}
</div>
)
}
);
},
},
{
title: "观看学员数",
key: "watchUserCount",
dataIndex: "watchUserCount",
align: "right",
title: '观看学员数',
key: 'watchUserCount',
dataIndex: 'watchUserCount',
align: 'right',
render: (val, item) => {
return val ? (
<div className='operate' style={{ display: "block" }} onClick={() => this.handleLinkToClassData(item)}>
<div className='operate' style={{ display: 'block' }} onClick={() => this.handleLinkToClassData(item)}>
<span className='operate__item'>{val}</span>
</div>
) : (
0
)
}
);
},
},
{
title: "",
title: '',
},
{
title: "操作",
key: "operate",
dataIndex: "operate",
title: '操作',
key: 'operate',
dataIndex: 'operate',
width: 160,
fixed: "right",
fixed: 'right',
render: (val, record, index) => {
return this.props.categoryId === "0" ? (
return this.props.categoryId === '0' ? (
<div className='operate'>
<div className='operate__item' onClick={() => this.handleDelete(record)}>
移出
......@@ -258,12 +257,12 @@ class KnowledgeBaseList extends React.Component {
</div>
) : (
<div className='operate'>
<div className={index === 0 && current === 1 ? "operate__item disable" : "operate__item"} onClick={() => this.handleUp(index, record)}>
<div className={index === 0 && current === 1 ? 'operate__item disable' : 'operate__item'} onClick={() => this.handleUp(index, record)}>
上移
</div>
<span className='operate__item split'> | </span>
<div
className={totalCount === size * (current - 1) + index + 1 ? "operate__item disable" : "operate__item"}
className={totalCount === size * (current - 1) + index + 1 ? 'operate__item disable' : 'operate__item'}
onClick={() => this.handleDown(record, index)}>
下移
</div>
......@@ -272,64 +271,64 @@ class KnowledgeBaseList extends React.Component {
移出
</div>
</div>
)
}
}
]
return columns
}
);
},
},
];
return columns;
};
handleDelete = (record) => {
return confirm({
title: "移出知识",
content: "确定将分类中此知识移出吗?",
title: '移出知识',
content: '确定将分类中此知识移出吗?',
icon: <span className='icon iconfont default-confirm-icon'>&#xe839; </span>,
okText: "删除",
okType: "danger",
cancelText: "取消",
okText: '删除',
okType: 'danger',
cancelText: '取消',
width: 440,
height: 188,
onOk: () => {
this.deleteConfirm(record)
}
})
}
this.deleteConfirm(record);
},
});
};
// 前往上课数据页面
handleLinkToClassData = (item) => {
const { match } = this.props;
localStorage.setItem("WatchData_CourseName", item.name);
localStorage.setItem('WatchData_CourseName', item.name);
window.RCHistory.push({
pathname: `${match.url}/course-data?type=${item.type}&id=${item.id}`
})
}
pathname: `${match.url}/course-data?type=${item.type}&id=${item.id}`,
});
};
deleteConfirm = (item) => {
const params = {
id: item.id,
storeId: item.storeId
}
storeId: item.storeId,
};
KnowledgeAPI.delKnowledge(params).then((res) => {
if (res.success) {
const { onChange, updateCategoryTree, selectedRowKeys, onSelectChange } = this.props;
message.success("移出成功");
message.success('移出成功');
onChange();
updateCategoryTree();
if (selectedRowKeys.includes(item.id)) {
onSelectChange(_.reject(selectedRowKeys, value => value === item.id));
onSelectChange(_.reject(selectedRowKeys, (value) => value === item.id));
}
}
})
}
});
};
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
let _query = this.props.query
_query.size = size
this.props.onChange(_query)
return;
}
let _query = this.props.query;
_query.size = size;
this.props.onChange(_query);
};
render() {
const { dataSource = [], totalCount, query, match, selectedRowKeys, onSelectChange } = this.props;
......@@ -338,21 +337,20 @@ class KnowledgeBaseList extends React.Component {
selectedRowKeys,
preserveSelectedRowKeys: true,
onChange: onSelectChange,
}
};
return (
<div className="knowledge-base-list">
<div className='knowledge-base-list'>
<XMTable
rowKey={(record) => record.id}
rowSelection={rowSelection}
size="middle"
size='middle'
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
className="knowledge-list-table"
className='knowledge-list-table'
renderEmpty={{
description: <span style={{ display: 'block', paddingBottom: 24 }}>暂无数据</span>
description: <span style={{ display: 'block', paddingBottom: 24 }}>暂无数据</span>,
}}
/>
......@@ -363,8 +361,8 @@ class KnowledgeBaseList extends React.Component {
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 }
this.props.onChange(_query)
const _query = { ...query, current: page + 1 };
this.props.onChange(_query);
}}
onShowSizeChange={this.onShowSizeChange}
/>
......@@ -375,8 +373,8 @@ class KnowledgeBaseList extends React.Component {
{this.state.scanFileModal}
</div>
)
);
}
}
export default withRouter(KnowledgeBaseList)
export default withRouter(KnowledgeBaseList);
......@@ -2,29 +2,30 @@
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-13 11:48:24
* @LastEditors: wufan
* @LastEditTime: 2021-08-09 15:53:08
* @LastEditors: yuananting
* @LastEditTime: 2021-08-16 16:38:20
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { Table, Modal, message, Tooltip, Switch, Dropdown } from "antd";
import React from 'react';
import { Table, Modal, message, Tooltip, Switch, Dropdown } from 'antd';
import { PageControl } from "@/components";
import _ from "underscore";
import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass";
import { appId, shareUrl, LIVE_SHARE } from "@/domains/course-domain/constants";
import TableSelectedData from "@/components/TableSelectedData";
import "./LiveList.less";
import CourseService from "@/domains/course-domain/CourseService";
import User from "@/common/js/user";
import { PageControl } from '@/components';
import _ from 'underscore';
import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import TableSelectedData from '@/components/TableSelectedData';
import './LiveList.less';
import CourseService from '@/domains/course-domain/CourseService';
import User from '@/common/js/user';
const ENV = process.env.DEPLOY_ENV || 'dev';
class VideoList extends React.Component {
constructor(props) {
super(props);
this.state = {
id: "", // 线上课ID
id: '', // 线上课ID
studentIds: [],
selectedRowKeys: [],
query: {
......@@ -40,8 +41,7 @@ class VideoList extends React.Component {
componentDidUpdate(prevProps, prevState) {
//必须写在if里面并且重新进行一次this.props !== prevProps的判断
if (
((this.props.courseName || this.props.courseType) &&
this.props.courseName !== prevProps.courseName) ||
((this.props.courseName || this.props.courseType) && this.props.courseName !== prevProps.courseName) ||
this.props.courseType !== prevProps.courseType
) {
this.handleFetchScheduleList(this.props);
......@@ -78,10 +78,10 @@ class VideoList extends React.Component {
let hours = Math.floor(time / 3600);
let mins = Math.floor(diff / 60);
let seconds = Math.floor(time % 60);
hours = hours < 10 ? "0" + hours : hours;
mins = mins < 10 ? "0" + mins : mins;
seconds = seconds < 10 ? "0" + seconds : seconds;
return hours + ":" + mins + ":" + seconds;
hours = hours < 10 ? '0' + hours : hours;
mins = mins < 10 ? '0' + mins : mins;
seconds = seconds < 10 ? '0' + seconds : seconds;
return hours + ':' + mins + ':' + seconds;
};
// 请求表头
parseColumns = () => {
......@@ -90,71 +90,58 @@ class VideoList extends React.Component {
title: (
<span>
<span>课程信息</span>
<Tooltip
title={
<div>
已加入该分类的课程不支持重复选择,因此不显示。
</div>
}
>
<Tooltip title={<div>已加入该分类的课程不支持重复选择,因此不显示。</div>}>
<i
className="icon iconfont"
className='icon iconfont'
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#bfbfbf",
fontSize: "14px",
fontWeight:"400"
}}
>
marginLeft: '5px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: '400',
}}>
&#xe61d;
</i>
</Tooltip>
</span>
),
key: "scheduleName",
dataIndex: "scheduleName",
key: 'scheduleName',
dataIndex: 'scheduleName',
width: 371,
render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record;
return (
<div className="record__item">
<div className='record__item'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img
className="course-cover"
src={
coverUrl ||
`${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
}
/>
<img className='course-cover' src={coverUrl || `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`} />
{record.courseName.length > 25 ? (
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
<div className='course-name'>{record.courseName}</div>
</Tooltip>
) : (
<div className="course-name">{record.courseName}</div>
<div className='course-name'>{record.courseName}</div>
)}
</div>
);
},
},
{
title: "课程时长",
key: "videoDuration",
dataIndex: "videoDuration",
title: '课程时长',
key: 'videoDuration',
dataIndex: 'videoDuration',
render: (text, item) => {
return <span>{text ? this.dealTimeDuration(text) : "-"}</span>;
return <span>{text ? this.dealTimeDuration(text) : '-'}</span>;
},
},
{
title: "课程分类",
key: "categoryName",
dataIndex: "categoryName",
title: '课程分类',
key: 'categoryName',
dataIndex: 'categoryName',
render: (val, record) => {
return (
<div className="record__item">
<div className='record__item'>
{record.categoryOneName}
{record.categoryTwoName ? `-${record.categoryTwoName}` : ""}
{record.categoryTwoName ? `-${record.categoryTwoName}` : ''}
</div>
);
},
......@@ -167,11 +154,7 @@ class VideoList extends React.Component {
let { selectedRowKeys } = this.state;
let _list = [];
if (selected || !_.find(selectedRowKeys, (item) => item.id == record.id)) {
_list = _.uniq(
selectedRowKeys.concat([record]),
false,
(item) => item.id
);
_list = _.uniq(selectedRowKeys.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectedRowKeys, (item) => item.id === record.id);
}
......@@ -182,27 +165,21 @@ class VideoList extends React.Component {
const { dataSource = [], totalCount, query, selectedRowKeys } = this.state;
const { current, size } = query;
const rowSelection = {
selectedRowKeys: _.pluck(selectedRowKeys, "id"),
selectedRowKeys: _.pluck(selectedRowKeys, 'id'),
onSelect: this.selectLiveList,
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(
selectedRowKeys.concat(changeRows),
false,
(item) => item.id
);
_list = _.uniq(selectedRowKeys.concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectedRowKeys, (item) =>
_.find(changeRows, (data) => data.id === item.id)
);
_list = _.reject(selectedRowKeys, (item) => _.find(changeRows, (data) => data.id === item.id));
}
this.setState({ selectedRowKeys: _list });
},
};
return (
<div className="live-list">
<div className='live-list'>
<TableSelectedData
selectedNum={selectedRowKeys.length}
clearSelectedData={() => {
......@@ -215,14 +192,14 @@ class VideoList extends React.Component {
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
size="middle"
size='middle'
rowSelection={rowSelection}
pagination={false}
bordered
className="video-list-table"
className='video-list-table'
/>
<div className="box-footer">
<div className='box-footer'>
{totalCount > 0 && (
<PageControl
current={current - 1}
......
import React, { useEffect, useState } from 'react';
import { withRouter } from "react-router-dom";
import { Tabs } from 'antd';
import { Route, withRouter } from 'react-router-dom';
import Service from '@/common/js/service';
import Breadcrumbs from "@/components/Breadcrumbs";
import UserLearningData from './UserLearningData';
import DataInfo from './components/DataInfo'
import CourseTable from './components/CourseTable';
import DataAnalysic from './components/DataAnalysic';
import ExamTable from './components/ExamTable';
import StudyTable from './components/StudyTable';
import './index.less'
const { TabPane } = Tabs;
function DataCenter(props: any) {
const { match: { params: { taskId } } } = props;
const { match } = props;
const { params: { taskId } } = match;
const [info, setInfo] = useState<any>({})
const [tabKey, setTabKey] = useState<any>('')
......@@ -44,8 +47,8 @@ function DataCenter(props: 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] || {};
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)
})
}
......@@ -72,7 +75,13 @@ function DataCenter(props: any) {
</Tabs>
</div>
<Route
path={`${match.url}/analysic/:id`}
render={() => {
return <DataAnalysic />;
}}
/>
<Route path={`${props.match.url}/user-learning-data/:storeCustomerId`} render={() => <UserLearningData taskId={taskId} />} />
</div>
}
......
import React, { useState, useRef, useEffect, useContext } from 'react'
import { Route, withRouter } from 'react-router-dom';
import Breadcrumbs from "@/components/Breadcrumbs";
import UserData from './UserData';
import ExamData from './ExamData'
import Service from "@/common/js/service";
import { Tabs } from 'antd';
import User from "@/common/js/user";
import './dataAnalysic.less'
const { TabPane } = Tabs;
function DataAnalysic(props: any) {
const examDetailInit: any = {};
const [selectKey, setSelectKey] = useState('user')
const [examDetail, setExamDetail] = useState(examDetailInit);
const { match } = props;
const examId =match.params.id;
useEffect(() => {
queryExamDetail();
}, [])
function queryExamDetail() {
Service.Hades("public/hades/queryExamDetail", {
examId:examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
const { result } = res
setExamDetail(result)
})
}
return <div className="page dataAnalysic">
<Breadcrumbs navList={"考试数据"} goBack={props.history.goBack} />
<div className="box">
<div className="titleBox ">
考试名称:{examDetail.examName}
</div>
</div>
<div className="box" style={{ paddingTop: 0 }}>
<Tabs activeKey={selectKey} onChange={(key: any) => {
setSelectKey(key)
}}>
<TabPane tab="考试人员数据" key="user">
<UserData examDetail ={examDetail } examId={examId} />
</TabPane>
<TabPane tab="题目数据" key="exam">
<ExamData examDetail ={examDetail } examId={examId}></ExamData>
</TabPane>
</Tabs>
</div>
</div>
}
export default withRouter(DataAnalysic);
\ No newline at end of file
import React, { useState, useRef, useEffect } from "react";
import Service from "@/common/js/service";
import { PageControl } from "@/components";
import { Input, Select, Tooltip, Button } from "antd";
import User from "@/common/js/user";
import { XMTable } from "@/components";
import college from "@/common/lottie/college.json";
import "./userData.less";
interface sortType {
type: "ascend" | "descend" | null | undefined;
}
function ExamData(props: any) {
const sortStatus: sortType = {
type: undefined,
};
const examDataInit: any = {};
const queryInit: any = { current: 1, size: 10, order: "SORT_ASC" };
const [examData, setUserData] = useState(examDataInit);
const [list, setList] = useState([]);
const [query, setQuery] = useState(queryInit);
const [total, setTotal] = useState(0);
const [field, setfield] = useState("");
const [allData, setAllData] = useState(0);
const [order, setOrder] = useState(sortStatus.type);
const questionTypeList = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
const userTypeEnum = {
WORK_WE_CHAT: "企业微信",
WE_CHAT: "微信",
};
const userExamStateEnum = {
EXAM: "进行中",
LACK_EXAM: "缺考",
FINISH_EXAM: "已考试",
};
const orderEnum = {
currentAccuracy: {
ascend: "ACCURACY_ASC",
descend: "ACCURACY_DESC",
},
};
const queryRef = useRef({});
useEffect(() => {
queryExamUserData();
}, []);
useEffect(() => {
queryRef.current = query;
queryExamUserDataList();
}, [query]);
function queryExamUserData() {
Service.Hades("public/hades/queryExamQuestionData", {
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setUserData(res.result);
});
}
function queryExamUserDataList() {
Service.Hades("public/hades/queryExamQuestionDataList", {
...query,
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setList(res.result.records);
setTotal(parseInt(res.result.total));
if (!allData) {
setAllData(parseInt(res.result.total));
}
});
}
const columns = [
{
title: "序号",
dataIndex: "sort",
width: 60,
render: (text: any, record: any, index: any) => <span>{index + 1}</span>,
},
{
title: "题目",
dataIndex: "questionStem",
ellipsis: true,
width: 350,
render: (val: any) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip
overlayClassName="aid-tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
</Tooltip>
);
},
},
{
title: "题型",
dataIndex: "questionType",
render: (text: any) => <span>{(questionTypeList as any)[text]}</span>,
filters: Object.keys(questionTypeList).map((key) => {
return {
text: (questionTypeList as any)[key],
value: key,
};
}),
},
{
title: "本次正确率",
dataIndex: "currentAccuracy",
sorter: true,
sortOrder: field === "currentAccuracy" ? order : sortStatus.type,
render: (text: any) => <span>{parseInt((text * 100) as any)}%</span>,
},
{
title: (
<div>
历史正确率{" "}
<Tooltip
overlayClassName="tool-list"
title="包含本次考试正确率"
placement="top"
overlayStyle={{ maxWidth: 700 }}
>
{" "}
<span
style={{ color: "rgba(191, 191, 191, 1)",fontWeight: 400 }}
className="icon iconfont"
>
&#xe61d;
</span>
</Tooltip>
</div>
),
dataIndex: "totalAccuracy",
render: (text: any) => <span>{parseInt((text * 100) as any)}%</span>,
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
console.log(filters, sorter);
setfield(sorter.field);
setOrder(sorter.order);
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field]);
let _query: any = { ...queryRef.current };
console.log(filters.questionType);
if (filters.questionType) {
console.log(233232);
_query.questionType = filters.questionType;
_query.current = 1;
} else {
delete _query.questionType;
}
_query.order = (orderEnum as any)[sorter.field][sorter.order];
setQuery(_query);
}
function download() {
Service.Hades("public/hades/exportExamData", {
// ...query,
examId: props.examId,
exportDataType: "EXAM_QUESTION_DATA",
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
const dom = (document as any).getElementById("load-play-back-excel");
dom.setAttribute("href", res.result);
dom.click();
});
}
return (
<div className="rr">
<a
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
></a>
<div className="dataPanal">
{!!examData.singleChoiceCnt && (
<div className="item">
<div className="num">
{Math.round((examData.singleChoiceAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fa;</span>单选题{" "}
<span>(共{examData.singleChoiceCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.multiChoiceCnt && (
<div className="item">
<div className="num">
{Math.round((examData.multiChoiceAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fb;</span>多选题
<span>(共{examData.multiChoiceCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.judgeCnt && (
<div className="item">
<div className="num">
{Math.round((examData.judgeAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fc;</span>判断题
<span>(共{examData.judgeCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.gapFillingCnt && (
<div className="item">
<div className="num">
{Math.round((examData.gapFillingAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fd;</span>填空题
<span>(共{examData.gapFillingCnt}题)</span>
</div>
</div>
</div>
)}
{!!examData.indefiniteChoiceCnt && (
<div className="item">
<div className="num">
{Math.round((examData.indefiniteChoiceAccuracy || 0) * 100)}%
</div>
<div className="percent">正确率</div>
<div className="subTitle">
<div className="type">
<span className="icon iconfont">&#xe7fe;</span>不定项选择题{" "}
<span>(共{examData.indefiniteChoiceCnt}题)</span>
</div>
</div>
</div>
)}
</div>
{!!allData && (
<Button style={{ marginBottom: 12, marginTop: 12 }} onClick={download}>
导出
</Button>
)}
<div className="content">
<XMTable
renderEmpty={{
image: college,
description: '暂无数据'
}}
bordered
size="small"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
></XMTable>
{total > 0 && (
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
console.log(page);
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query);
}}
/>
)}
</div>
</div>
);
}
export default ExamData;
import React, { useEffect, useState } from 'react';
import { withRouter } from "react-router-dom";
import { Route, withRouter } from 'react-router-dom';
import { PageControl, XMTable } from '@/components';
import Service from '@/common/js/service';
import User from '@/common/js/user';
function ExamTable(props: any) {
const { match } = props;
console.log(match)
const [query, setQuery] = useState<any>({
current: 1, size: 10,
taskId: props.taskId,
......@@ -22,8 +25,8 @@ function ExamTable(props: any) {
function getList() {
Service.Hades('public/hades/queryTrainingExamUserData', query).then((res: any) => {
setList(res.result.records)
setTotal(res.result.tatal)
setList(res.result.records);
setTotal(res.result.total);
})
}
......@@ -79,7 +82,11 @@ function ExamTable(props: any) {
render: (val: any, record: any) => {
return (
<div className='operate-area'>
<span className='operate-item' onClick={() => { }}>
<span className='operate-item' style={{ color: 'rgba(41, 102, 255, 1)', cursor: 'pointer' }} onClick={() => {
props.history.push({
pathname: `${match.url}/analysic/${record.examId}`,
});
}}>
查看数据
</span>
......@@ -90,6 +97,7 @@ function ExamTable(props: any) {
];
return <div className="study_Table">
<div style={{ marginTop: 12 }}>
......
......@@ -31,12 +31,12 @@ function LeftStageList(props) {
>
<div className="icon">
{item.isShowMoreCourse ? (
<span className="icon iconfont edit-icon">&#xe6b2;</span>
<span className="icon iconfont edit-icon">&#xe677;</span>
) : (
<span className="icon iconfont edit-icon">&#xe600;</span>
)}
</div>
<div className="stage-name">
<div className="stage-name oneLineText">
{ENUM.IndexToSort[index + 1]}{item.stageName}
</div>
</div>
......
......@@ -8,8 +8,7 @@ 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;
......@@ -294,7 +293,7 @@ function StudyTable(props: any) {
</div>
)}
</div>
<Route path={`${props.match.url}/user-learning-data/:storeCustomerId`} render={() => <UserLearningData taskId={props.taskId} />} />
</div>
}
......
import React, { useState, useRef, useEffect } from "react";
import Service from "@/common/js/service";
import { PageControl } from "@/components";
import { Input, Select, Tooltip, Table, Button } from "antd";
import { ColumnsType } from "antd/es/table";
import User from "@/common/js/user";
import moment from "moment";
import { XMTable } from "@/components";
import college from "@/common/lottie/college.json";
import "./userData.less";
const { Search } = Input;
const { Option } = Select;
declare var window: any;
interface sortType {
type: "ascend" | "descend" | null | undefined;
}
interface User {
key: number;
name: string;
}
function DataAnalysic(props: any) {
const sortStatus: sortType = {
type: undefined,
};
const useDataInit: any = {};
const queryInit: any = { current: 1, size: 10 };
const [useData, setUserData] = useState(useDataInit);
const [list, setList] = useState([]);
const [query, setQuery] = useState(queryInit);
const [total, setTotal] = useState(0);
const [field, setfield] = useState("");
const [allData, setAllData] = useState(0);
const [order, setOrder] = useState(sortStatus.type);
const userTypeEnum = {
WORK_WE_CHAT: "企业微信",
WE_CHAT: "微信",
};
const userExamStateEnum = {
EXAM: "进行中",
LACK_EXAM: "缺考",
FINISH_EXAM: "已考试",
};
const ExamPassColorEnum = {
EXAM_FAIL: "rgba(255, 79, 79, 1)",
EXAM_PASS: "rgba(59, 189, 170, 1)",
};
const ExamPassEnum = {
EXAM_FAIL: "不及格",
EXAM_PASS: "及格",
};
const userExamStateColorEnum = {
EXAM: "rgba(35, 143, 255, 1)",
LACK_EXAM: "rgba(204, 204, 204, 1)",
FINISH_EXAM: "rgba(47, 200, 60, 1)",
};
const orderEnum = {
score: {
ascend: "EXAM_SCORE_ASC",
descend: "EXAM_SCORE_DESC",
},
userDuration: {
ascend: "USER_DURATION_ASC",
descend: "USER_DURATION_DESC",
},
};
const queryRef = useRef({});
useEffect(() => {
queryExamUserData();
}, []);
useEffect(() => {
queryRef.current = query;
queryExamUserDataList();
}, [query]);
function queryExamUserData() {
Service.Hades("public/hades/queryExamUserData", {
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setUserData(res.result);
});
}
function queryExamUserDataList() {
Service.Hades("public/hades/queryExamUserDataList", {
...query,
examId: props.examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
setList(res.result.records);
setTotal(parseInt(res.result.total));
if (!allData) {
setAllData(parseInt(res.result.total));
}
});
}
const columns: ColumnsType<User> = [
{
title: "学员",
dataIndex: "userName",
render: (text: any, record: any) => (
<span>
{text}
<span
style={{
color:
record.userSource === "WORK_WE_CHAT"
? "rgba(255, 157, 20, 1)"
: "rgba(29, 204, 101, 1)",
}}
>
@{(userTypeEnum as any)[record.userSource]}
</span>
</span>
),
},
{
title: "手机号",
dataIndex: "phone",
},
{
title: "考试状态",
dataIndex: "userExamState",
render: (text: any) => (
<span>
{" "}
<span
className="exstatus"
style={{ background: (userExamStateColorEnum as any)[text] }}
></span>{" "}
{(userExamStateEnum as any)[text]}
</span>
),
},
{
title: "考试成绩",
dataIndex: "score",
sorter: true,
sortOrder: field === "score" ? order : sortStatus.type,
render: (text: any, record: any) => (
<span>
{" "}
{text}{" "}
<span
style={{
border: `1px solid ${
(ExamPassColorEnum as any)[record.examPass]
}`,
fontSize: 12,
color: (ExamPassColorEnum as any)[record.examPass],
display: "inline-block",
padding: "0px 2px",
}}
>
{(ExamPassEnum as any)[record.examPass]}
</span>
</span>
),
},
{
title: "进入考试时间",
dataIndex: "examStartTime",
render: (text: any) => (
<span>{moment(text).format("YYYY-MM-DD HH:mm")}</span>
),
},
{
title: "考试用时",
dataIndex: "userDuration",
sorter: true,
sortOrder: field === "userDuration" ? order : sortStatus.type,
render: (text: any, record: any) => (
<span>
{record.userExamState === "FINISH_EXAM"
? window.formatHourTime(text)
: "-"}{" "}
</span>
),
},
//TODO:
{
title: "操作",
key: "",
dataIndex: "edit",
render: (value: any, record: any) => {
return (
<Choose>
<When condition={record.userExamState === "FINISH_EXAM"}>
<div
className="answer-detail"
onClick={() => {
checkAnswerDetail(record);
}}
>
答题详情
</div>
</When>
<Otherwise>-</Otherwise>
</Choose>
);
},
},
];
function onChange(pagination: any, filters: any, sorter: any, extra: any) {
setfield(sorter.field);
setOrder(sorter.order);
console.log(sorter.field, sorter.order, (orderEnum as any)[sorter.field]);
let _query: any = { ...queryRef.current };
_query.order = (orderEnum as any)[sorter.field][sorter.order];
setQuery(_query);
}
function download() {
Service.Hades("public/hades/exportExamData", {
// ...query,
examId: props.examId,
exportDataType: "EXAM_USER_DATA",
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
const dom = (document as any).getElementById("load-play-back-excel");
dom.setAttribute("href", res.result);
dom.click();
});
}
//查看答题详情
function checkAnswerDetail(record: any) {
const { paperId, userExamState } = props.examDetail?.examPaper;
const { userId } = record;
window.RCHistory.push({
pathname: `/test-detail/${props.examId}?paperId=${paperId}&userExamState=${userExamState}&userId=${userId}`,
});
}
return (
<div className="rr">
<a
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
>
111
</a>
<div className="dataPanal">
<div className="item">
<div className="num">{useData.joinCnt || 0}</div>
<div className="percent"></div>
<div className="subTitle">参与人数</div>
</div>
<div className="item">
<div className="num">{useData.finishCnt || 0}</div>
<div className="percent">
占比
{parseInt(
((useData.finishCnt || 0) / (useData.joinCnt || 1)) * 100 + ""
)}
%
</div>
<div className="subTitle">完成考试数 (人)</div>
</div>
<div className="item">
<div className="num">{useData.passCnt || 0}</div>
<div className="percent">
占比
{parseInt(
((useData.passCnt || 0) / (useData.finishCnt || 1)) * 100 + ""
)}
%
</div>
<div className="subTitle">及格数 (人)</div>
</div>
<div className="item">
<div className="num">{useData.averageScore || 0}</div>
<div className="percent">
总分{props.examDetail?.examPaper?.totalScore}
</div>
<div className="subTitle">平均分</div>
</div>
<div className="item">
<div className="num">
{" "}
{window.formatHourTime(useData.averageDuration || 0)}{" "}
</div>
<div className="percent"></div>
<div className="subTitle">平均用时</div>
</div>
</div>
<div className="xm-search-filter" style={{ marginTop: "24px" }}>
<div style={{ display: "flex" }}>
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">学员:</span>
<Search
value={query.examName}
className="search-input"
placeholder="搜索学员名或手机号"
onChange={(e) => {
const _query = { ...query };
_query.searchKey = e.target.value;
setQuery(_query);
}}
onSearch={() => {}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="search-name">学员类型:</span>
<Select
value={query.userSource}
placeholder="请选择学员类型"
onChange={(val) => {
const _query = { ...query };
_query.userSource = val;
setQuery(_query);
}}
className="search-input"
allowClear
>
{Object.keys(userTypeEnum).map((key: any) => {
return (
<Option value={key} key={key}>
{(userTypeEnum as any)[key]}
</Option>
);
})}
</Select>
</div>
<div className="search-condition__item">
<span className="search-name">考试状态:</span>
<Select
value={query.userExamState}
placeholder="请选择考试状态"
onChange={(val) => {
const _query = { ...query };
_query.userExamState = val;
setQuery(_query);
}}
className="search-input"
allowClear
>
{Object.keys(userExamStateEnum).map((key: any) => {
return (
<Option value={key} key={key}>
{(userExamStateEnum as any)[key]}
</Option>
);
})}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={() => {
setfield("");
setQuery({ current: 1, size: 10 });
}}
>
&#xe61b;{" "}
</span>
</Tooltip>
</div>
</div>
</div>
{!!allData && (
<Button style={{ marginBottom: 12 }} onClick={download}>
导出
</Button>
)}
<div className="content analysic-content">
<XMTable
renderEmpty={{
image: college,
description: '暂无数据'
}}
bordered
size="small"
rowClassName="analysic-content-row"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
></XMTable>
{total > 0 && (
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
toPage={(page: any) => {
console.log(page);
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query);
}}
/>
)}
</div>
</div>
);
}
export default DataAnalysic;
.dataAnalysic {
.titleBox {
position: relative;
font-size: 19px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
background: #ffffff;
&::before {
width: 4px;
height: 12px;
content: '';
background-image: linear-gradient(#2966ff 83.5%, #0acca4 16.5%);
display: inline-block;
margin-right: 8px;
}
}
.ant-tabs-content-holder {
margin-top: 8px;
}
}
.dataPanal {
border-radius: 4px;
border: 1px solid #e8e8e8;
display: flex;
.item {
text-align: center;
// width: 29.9%;
position: relative;
flex: 1;
.num {
font-size: 26px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
margin-top: 12px;
}
.percent {
margin-top: 6px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 17px;
height: 20px;
margin-bottom: 18px;
}
.subTitle {
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 12px;
}
.type {
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
span {
color: rgba(153, 153, 153, 1);
}
.icon {
color: rgba(204, 204, 204, 1);
font-size: 16px;
margin-right: 4px;
position: relative;
top: 1px;
}
}
&:after {
content: '';
width: 0px;
height: 40px;
position: absolute;
width: 1px;
background-color: rgba(232, 232, 232, 1);
top: 40px;
right: 0px;
}
&:last-child {
&:after {
display: none;
}
}
}
.exstatus {
width: 4px;
height: 4px;
background: rgb(35, 143, 255);
display: inline-block;
border-radius: 50%;
position: relative;
top: -4px;
}
}
.answer-detail {
color: rgb(35, 143, 255);
}
.analysic-content {
.ant-table-bordered .ant-table-tbody tr {
&.analysic-content-row {
height: 50px;
}
}
}
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-08-03 17:05:32
* @LastEditors: yuananting
* @LastEditTime: 2021-08-11 11:49:11
* @LastEditTime: 2021-08-16 16:36:59
* @Description: 新建培训任务-关联考试抽屉
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
......@@ -65,7 +65,7 @@ function RelatedExamDrawer(props) {
paperId,
examName,
passRate,
examDuration,
examDuration: (examDuration || 0) * 60 * 1000,
examDesc,
needOptionDisorder,
resultShow,
......
......@@ -2,13 +2,13 @@
* @Author: yuananting
* @Date: 2021-08-05 17:09:36
* @LastEditors: yuananting
* @LastEditTime: 2021-08-13 19:18:54
* @LastEditTime: 2021-08-16 14:47:51
* @Description: 新建培训任务-选择指派对象
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { Modal, Tree, Tooltip, AutoComplete, Tabs } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import { Modal, Tree, Tooltip, AutoComplete, Tabs, Input, Checkbox } from 'antd';
import User from '@/common/js/user';
import { DepType } from '@/domains/store-domain/constants';
import StoreService from '@/domains/store-domain/storeService';
......@@ -17,22 +17,38 @@ import './ChooseAssignorModal.less';
const { TabPane } = Tabs;
const { DirectoryTree } = Tree;
const { Search } = Input;
function ChooseAssignorModal(props) {
const AssignTypeEnum = {
departMentTab: 'SECTION',
postGrouptab: 'POST',
customGroupTab: 'CUSTOM',
};
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 [queryName, setQueryName] = useState(''); // 搜索框内的值
const queryNameRef = useRef(null);
const timer = useRef(null);
const AssignTypeEnum = {
departMentTab: 'SECTION',
postGrouptab: 'POST',
customGroupTab: 'CUSTOM',
};
useEffect(() => {
queryNameRef.current = queryName;
setOpen(!!queryName);
clearTimeout(timer.current);
timer.current = setTimeout(() => {
if (!!queryName) return getCompleteOptionData(queryNameRef.current);
setCompleteOption([]);
}, 500);
}, [queryName]);
useEffect(() => {
setQueryName(''); // 切换tab时搜索置空
setCompleteOption([]);
setStructureData([]);
getStructureData();
}, [activeKey]);
......@@ -40,7 +56,7 @@ function ChooseAssignorModal(props) {
const params = {
depType: DepType[activeKey],
enterpriseId: User.getEnterpriseId(),
source: 0, //0代表来自企培
source: 0, // 0代表来自企培
storeId: User.getStoreId(),
userId: User.getUserId(),
whetherCount: false,
......@@ -117,14 +133,16 @@ function ChooseAssignorModal(props) {
}
function renderTitle(title) {
return <span>{title}</span>;
return <span className='catalog-title'>{title}</span>;
}
function renderItem(record, type) {
return {
value: record.userName || record.name,
label: (
<Checkbox>
<div
className='search-result-item'
style={{
display: 'flex',
justifyContent: 'space-between',
......@@ -132,28 +150,35 @@ function ChooseAssignorModal(props) {
depId={record.id}
type={type}>
{type === 'user' ? (
<div>
<div className='search-result-item__left'>
<WWOpenDataCom type='userName' openid={record.userName} />
</div>
) : (
<div>
<WWOpenDataCom type='departmentName' openid={record.name} />
<div className='search-result-item__left'>
{activeKey === 'departMentTab' ? <WWOpenDataCom type='departmentName' openid={record.name} /> : <span>{record.name}</span>}
</div>
)}
{type === 'user' &&
record.postDepNamesList.map((item, index) => {
{type === 'user' && (
<div className='search-result-item__right'>
<Tooltip title={<div>{handleDepName(record.depNamesList)}</div>} placement='top' arrowPointAtCenter>
{record.depNamesList.map((item, index) => {
return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
{index < record.depNamesList.length - 1 ? ';' : ''}
</span>
);
})}
</Tooltip>
</div>
)}
{type === 'post' && (
<span type='post' openid={record.parentName}>
{record.parentName}
</span>
)}
</div>
</Checkbox>
),
};
}
......@@ -161,7 +186,7 @@ function ChooseAssignorModal(props) {
function getCompleteOptionData(value) {
setCompleteOption([]);
const params = {
depType: DepType[props.treeType],
depType: DepType[activeKey],
queryName: value,
enterpriseId: User.getEnterpriseId(),
source: 0, //0代表来自企培
......@@ -185,7 +210,7 @@ function ChooseAssignorModal(props) {
});
}
if (departmentVOList.length > 0) {
switch (props.treeType) {
switch (activeKey) {
case 'departMentTab':
departmentGroupObj.label = renderTitle('部门');
break;
......@@ -203,7 +228,7 @@ function ChooseAssignorModal(props) {
});
}
if (subLevelDepartmentVOList.length > 0) {
switch (props.treeType) {
switch (activeKey) {
case 'postGrouptab':
postobj.label = renderTitle('岗位');
break;
......@@ -220,27 +245,51 @@ function ChooseAssignorModal(props) {
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);
}
if (Object.keys(departmentGroupObj).length !== 0) {
_completeOptions.push(departmentGroupObj);
}
setCompleteOption(_completeOptions);
});
}
// 搜索空状态渲染;
function notFoundContentNode() {
return (
<div className='empty-con'>
<img src='https://image.xiaomaiketang.com/xm/wRDrb2pJFb.png' className='empty-img' />
<div className='empty-text'>暂无数据</div>
</div>
);
}
function confirmSearchSelect(value, option) {
const param = {};
setOpen(false);
setQueryName(value);
if (option.label.props.type === 'user') {
param.queryName = value;
} else {
param.id = option.label.props.depId;
}
props.searchUserList(param, activeKey, 1);
}
// 弱提示渲染
function handlePlaceHolder() {
let placeholder = '';
switch (props.treeType) {
switch (activeKey) {
case 'departMentTab':
placeholder = '搜索学员姓名、部门';
placeholder = '搜索学员、部门';
break;
case 'postGrouptab':
placeholder = '搜索学员姓名/岗位/岗位组';
placeholder = '搜索学员、岗位、岗位组';
break;
case 'customGroupTab':
placeholder = '搜索学员姓名/自定义分组集合/自定义分组';
placeholder = '搜索学员、分组、分组集合';
break;
default:
break;
......@@ -248,6 +297,17 @@ function ChooseAssignorModal(props) {
return placeholder;
}
function handleDepName(depArray) {
const depArrayDom = depArray.map((item, index) => {
return (
<span>
<WWOpenDataCom type='departmentName' openid={item} />
</span>
);
});
return depArrayDom;
}
return (
<Modal
className='choose-assignor-modal'
......@@ -261,16 +321,13 @@ function ChooseAssignorModal(props) {
maskClosable={false}>
<div className='assignor-container'>
<div className='left-list'>
{/* <AutoComplete
dropdownClassName='certain-category-search-dropdown'
<AutoComplete
dropdownClassName='left-search-dropdown'
dropdownMatchSelectWidth={272}
allowClear
onChange={(value) => setSearchKey(value)}
onSearch={(value) => {
getCompleteOptionData(value);
}}
// notFoundContent={notFoundContentNode()}
value={searchKey}
onChange={(value) => setQueryName(value)}
notFoundContent={notFoundContentNode()}
value={queryName}
open={open}
onFocus={() => {
setOpen(true);
......@@ -283,7 +340,9 @@ function ChooseAssignorModal(props) {
}}
options={completeOptions}
// onSelect={confirmSearchSelect}
placeholder={handlePlaceHolder()}></AutoComplete> */}
placeholder={handlePlaceHolder()}>
<Search style={{ width: 272 }} enterButton={<span className='icon iconfont'>&#xe832;</span>} />
</AutoComplete>
<div className='data-body'>
<Tabs size={'small'} onChange={(key) => setActiveKey(key)}>
<TabPane key='departMentTab' tab='部门'></TabPane>
......
......@@ -5,8 +5,12 @@
.left-list {
width: 50%;
margin-right: 24px;
padding: 0 0 12px 16px;
padding: 12px 0 12px 16px;
border: 1px solid #e8e8e8;
.ant-select-auto-complete .ant-select-clear {
font-size: 14px;
right: 15px;
}
.data-body {
.ant-tabs-nav {
margin: 0 !important;
......@@ -18,7 +22,7 @@
.tree-con {
overflow-y: scroll;
overflow-x: hidden;
max-height: 367px;
max-height: 323px;
padding-right: 16px;
.ant-tree .ant-tree-treenode {
padding: 12px 0 !important;
......@@ -102,3 +106,51 @@
}
}
}
.left-search-dropdown {
.catalog-title {
font-size: 14px;
color: #666;
margin-bottom: 14px;
}
.search-result-item {
padding: 14px 0;
color: #333;
.title-icon {
color: #999;
margin-right: 3px;
}
.search-result-item__left {
font-size: 14px;
}
.search-result-item__right {
font-size: 14px;
width: 84px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #999;
text-align: right;
}
}
.ant-select-dropdown-menu-item-group-title {
color: #666;
font-weight: bold;
}
.ant-select-item-option-grouped {
padding-left: 12px;
}
.ant-select-item-option-content {
padding-left: 24px;
}
.empty-con {
text-align: center;
.empty-img {
width: 150px;
height: 150px;
}
.empty-text {
color: #666;
}
}
}
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