Commit 4a8eb900 by guomingpang

feat:新增答题详情与

parent 7becb496
......@@ -3,7 +3,7 @@
* @Date: 2020-08-31 09:34:25
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-05-12 17:27:08
* @Description:
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -11,32 +11,34 @@ import Storage from './storage';
import { PREFIX, USER_PREFIX } from '@/domains/basic-domain/constants';
class User {
getStoreId(){
return Storage.get(`${PREFIX}_storeId`)
getStoreId() {
return Storage.get(`${PREFIX}_storeId`);
}
getEnterpriseId() {
return Storage.get(`${PREFIX}_enterpriseId`)
return Storage.get(`${PREFIX}_enterpriseId`);
}
getStoreName(){
return Storage.get(`${PREFIX}_storeName`)
getStoreName() {
return Storage.get(`${PREFIX}_storeName`);
}
getStoreType(){
return Storage.get(`${PREFIX}_storeType`)
getStoreType() {
return Storage.get(`${PREFIX}_storeType`);
}
getStoreUserId(){
return Storage.get(`${PREFIX}_storeUserId`)
getStoreUserId() {
return Storage.get(`${PREFIX}_storeUserId`);
}
getUserId(){
return Storage.get(`${PREFIX}_userId`)
getCustomerId() {
return Storage.get(`${PREFIX}_customerId`);
}
getUserRole(){
return Storage.get(`${PREFIX}_userRole`)
getUserId() {
return Storage.get(`${PREFIX}_userId`);
}
getUserRole() {
return Storage.get(`${PREFIX}_userRole`);
}
getToken() {
......@@ -47,67 +49,71 @@ class User {
return Storage.get(`${PREFIX}_isAdmin`);
}
setStoreId(value:any){
return Storage.set(`${PREFIX}_storeId`,value)
setStoreId(value: any) {
return Storage.set(`${PREFIX}_storeId`, value);
}
setEnterpriseId(value: any) {
return Storage.set(`${PREFIX}_enterpriseId`,value)
return Storage.set(`${PREFIX}_enterpriseId`, value);
}
setStoreName(value: any) {
return Storage.set(`${PREFIX}_storeName`, value);
}
setStoreName(value:any){
return Storage.set(`${PREFIX}_storeName`,value)
setStoreType(value: any) {
return Storage.set(`${PREFIX}_storeType`, value);
}
setStoreType(value:any){
return Storage.set(`${PREFIX}_storeType`,value)
setStoreUserId(value: any) {
return Storage.set(`${PREFIX}_storeUserId`, value);
}
setStoreUserId(value:any){
return Storage.set(`${PREFIX}_storeUserId`,value)
setCustomerId(value: any) {
return Storage.set(`${PREFIX}_customerId`, value);
}
setUserId(value:any){
return Storage.set(`${PREFIX}_userId`,value)
setUserId(value: any) {
return Storage.set(`${PREFIX}_userId`, value);
}
setUserRole(value:any){
return Storage.set(`${PREFIX}_userRole`,value)
setUserRole(value: any) {
return Storage.set(`${PREFIX}_userRole`, value);
}
setToken(value:any) {
return Storage.set(`${PREFIX}_token`,value);
setToken(value: any) {
return Storage.set(`${PREFIX}_token`, value);
}
setIsAdmin(value: any) {
return Storage.set(`${PREFIX}_isAdmin`, value);
}
removeToken(){
removeToken() {
return Storage.remove(`${PREFIX}_token`);
}
removeUserId(){
removeUserId() {
return Storage.remove(`${PREFIX}_userId`);
}
removeEnterpriseId() {
return Storage.remove(`${PREFIX}_enterpriseId`)
return Storage.remove(`${PREFIX}_enterpriseId`);
}
getCustomerStoreId(){
getCustomerStoreId() {
return Storage.get(`${PREFIX}_customerStoreId`);
}
setCustomerStoreId(value:any) {
return Storage.set(`${PREFIX}_customerStoreId`,value);
setCustomerStoreId(value: any) {
return Storage.set(`${PREFIX}_customerStoreId`, value);
}
setIdentifier(value:any){
return Storage.set(`${PREFIX}_identifier`,value);
setIdentifier(value: any) {
return Storage.set(`${PREFIX}_identifier`, value);
}
getIdentifier(){
getIdentifier() {
return Storage.get(`${PREFIX}_identifier`);
}
clearUserInfo(){
clearUserInfo() {
Storage.remove(`${USER_PREFIX}_token`);
Storage.remove(`${USER_PREFIX}_userId`);
Storage.remove(`${USER_PREFIX}_userPhone`);
......@@ -119,4 +125,4 @@ class User {
}
}
export default new User();
\ No newline at end of file
export default new User();
......@@ -7,7 +7,7 @@
left: 0px;
right: 0;
bottom: 0;
z-index:3;
z-index: 3;
background-color: #fff;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
......@@ -20,12 +20,12 @@
bottom: 0;
z-index: 102;
overflow: auto;
margin:0 16px;
.box{
&:first-child{
margin: 0 16px;
.box {
&:first-child {
margin-bottom: 8px;
}
&:last-child{
&:last-child {
margin-bottom: 16px;
}
}
......@@ -48,10 +48,10 @@
.content-header {
padding: 16px 16px 8px 16px;
line-height: 30px;
font-size:24px;
color:#333;
font-weight:bold;
background: #FFF;
font-size: 24px;
color: #333;
font-weight: bold;
background: #fff;
h1 {
font-weight: normal;
display: inline-block;
......
import React from 'react'
import { Tabs } from 'antd'
import VideoCourseFilter from './components/VideoCourseFilter'
import VideoCourseOpt from './components/VieoCourseOpt'
import VideoCourseList from './components/VideoCourseList'
import CourseService from '@/domains/course-domain/CourseService'
import User from '@/common/js/user'
const { TabPane } = Tabs
import React from 'react';
import { Tabs } from 'antd';
import VideoCourseFilter from './components/VideoCourseFilter';
import VideoCourseOpt from './components/VieoCourseOpt';
import VideoCourseList from './components/VideoCourseList';
import CourseService from '@/domains/course-domain/CourseService';
import User from '@/common/js/user';
const { TabPane } = Tabs;
class VideoCourse extends React.Component {
constructor(props) {
super(props)
super(props);
this.state = {
query: {
size: 10,
......@@ -18,45 +18,43 @@ class VideoCourse extends React.Component {
dataSource: [], // 视频课列表
totalCount: 0, // 视频课数据总条数
currentTabKey: 'internal',
}
};
}
componentWillMount() {
// 获取视频课列表
this.handleFetchScheduleList()
this.handleFetchScheduleList();
}
// 获取视频课列表
handleFetchScheduleList = (_query = {}) => {
const { currentTabKey } = this.state
const { currentTabKey } = this.state;
const query = {
...this.state.query,
..._query,
courseDivision: currentTabKey === 'external' ? 1 : null,
}
// 更新请求参数
this.setState({ query })
};
CourseService.videoSchedulePage(query).then((res) => {
const { result = {} } = res || {}
const { records = [], total = 0 } = result
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
if (Number(total) && query.size * (query.current - 1) >= Number(total)) {
this.handleFetchScheduleList({
...query,
current: 1,
})
return
});
return;
}
this.setState({
query,
dataSource: records,
totalCount: Number(total),
})
})
}
});
});
};
currenTabChange = (currentTabKey) => {
const { query } = this.state
const { query } = this.state;
this.setState(
{
currentTabKey,
......@@ -66,20 +64,21 @@ class VideoCourse extends React.Component {
},
},
() => {
this.handleFetchScheduleList()
console.log('this.state.query===>', this.state.query);
// this.handleFetchScheduleList()
}
)
}
);
};
changeShelfState = (index, shelfState) => {
const { dataSource } = this.state
dataSource[index].shelfState = shelfState
const { dataSource } = this.state;
dataSource[index].shelfState = shelfState;
this.setState({
dataSource,
})
}
});
};
render() {
const { dataSource, totalCount, query, currentTabKey } = this.state
const { dataSource, totalCount, query, currentTabKey } = this.state;
return (
<div className='page video-course-page'>
<div className='content-header'>视频课</div>
......@@ -107,8 +106,8 @@ class VideoCourse extends React.Component {
/>
</div>
</div>
)
);
}
}
export default VideoCourse
export default VideoCourse;
/*
* @Author: yuananting
* @Date: 2021-04-08 15:50:52
* @LastEditors: wufan
* @LastEditTime: 2021-04-24 15:55:19
* @Description: 助学工具-考试-答案详情
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect, useRef } from 'react';
import { Route, withRouter } from 'react-router-dom';
import User from '@/common/js/user';
import Service from '@/common/js/service';
import './AnswerDescPage.less';
import { message } from 'antd';
import XMAudio from './XMAudio';
// import ScanFileModal from '@/component/ScanFileModal';
const NUM_TO_WORD_MAP = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
function AnswerDescPage(props) {
const examId = props.match.params.testId.replace(/\?.+/, '');
const paperId = window.getParameterByName('paperId');
const [customerId, setCustomerId] = useState('');
const [examDetail, setExamDetail] = useState({
examDesc: '',
examDuration: 0,
examEndTime: '',
examName: '',
examStartTime: '',
paperId: 0,
passRate: 0,
passScore: 0,
resultContent: '',
resultShow: '',
totalQuestionCount: 0,
totalScore: 0,
userCorrectQuestion: 0,
userExamDuration: 0,
userExamState: '',
userScore: 0,
});
const [paperDetail, setPaperDetail] = useState({}); // 试卷详情
const [userAnswerList, setUserAnswerList] = useState([]); // 用户当前答案列表
const [allUserAnswerList, setAllUserAnswerList] = useState([]); // 用户所有答案列表
const [errorUserAnswerList, setErrorUserAnswerList] = useState([]); // 用户错误答案列表
const [questionList, setQuestionList] = useState([]); // 用户当前题目列表
const [allQuestionList, setAllQuestionList] = useState([]); // 用户所有题目列表
const [errorQuestionList, setErrorQuestionList] = useState([]); // 用户错误题目列表
const [errorCount, setErrorCount] = useState(0);
const [onlyError, setOnlyError] = useState(true); // 只显示错题
const [activeIndex, setActiveIndex] = useState(0); // 当前答题索引
const [activeOrderIndex, setActiveOrderIndex] = useState(0); // 当前答题序号
const [isShowQuestionCard, setIsShowQuestionCard] = useState(false); // 答题卡是否展开
const [isShowErrorPage, setIsShowErrorPage] = useState(false); // 是否展示异常提示页面
// 是否打开预览文件弹框
const [showScanFile, setShowScanFile] = useState(false);
const [scanFileAddress, setScanFileAddress] = useState(false);
const [scanFileType, setScanFileType] = useState(false);
const questionTypeList = {
SINGLE_CHOICE: '单选题',
MULTI_CHOICE: '多选题',
JUDGE: '判断题',
GAP_FILLING: '填空题',
INDEFINITE_CHOICE: '不定项选择题',
};
useEffect(() => {
getPaperDetail();
getExamDetail();
}, []);
// 获取考试详情
function getExamDetail() {
const params = {
examId,
source: 0,
tenantId: User.getStoreId() || window.getParameterByName('id'),
userId: User.getCustomerId(),
};
Service.Hades('public/customerHades/queryUserExamResult', params, {
reject: true,
})
.then((res) => {
const data = { ...res.result };
setExamDetail(data);
})
.catch((res) => {
if (res.code === 'EXAM_IS_NOT_EXIST') {
handleChangeShowErrorPage();
} else {
message.error(res.message);
}
});
}
// 获取试卷详情
function getPaperDetail() {
Service.Hades('public/customerHades/queryUserExamAnswer', {
examId,
paperId,
readAnswer: true,
source: 0,
tenantId: User.getStoreId() || window.getParameterByName('id'),
userId: User.getCustomerId(),
}).then((res) => {
const { paperDetailVO, userExamAnswerVO } = res.result;
const paperDetail = { ...paperDetailVO };
const allUserAnswerList = [...userExamAnswerVO];
let allQuestionList = [...paperDetail.questionList];
allQuestionList = allQuestionList.map((item, index) => {
item.orderIndex = index;
return item;
});
const errorUserAnswerList = _.filter(allUserAnswerList, (item) => {
return item.isCorrect === 0;
});
let errorQuestionList = [];
let userAnswerMap = {};
errorUserAnswerList.forEach((item) => {
userAnswerMap[item.questionId] = true;
});
allQuestionList.forEach((item) => {
if (userAnswerMap[item.questionId]) {
errorQuestionList.push(item);
}
});
setActiveOrderIndex(errorQuestionList.length > 0 ? errorQuestionList[0].orderIndex : 0);
setPaperDetail(paperDetail);
setAllUserAnswerList(allUserAnswerList);
setErrorUserAnswerList(errorUserAnswerList);
setErrorCount(errorUserAnswerList.length);
setUserAnswerList(errorUserAnswerList);
setAllQuestionList(allQuestionList);
setErrorQuestionList(errorQuestionList);
setQuestionList(errorQuestionList);
});
}
function handleChangeShowErrorPage() {
setIsShowErrorPage(true);
}
function handleChangeActiveIndex(isPre) {
if (onlyError) {
if (isPre && activeOrderIndex !== errorQuestionList[0].orderIndex) {
setActiveOrderIndex(errorQuestionList[activeIndex - 1].orderIndex);
setActiveIndex(activeIndex - 1);
} else if (!isPre && activeOrderIndex !== errorQuestionList[errorCount - 1].orderIndex) {
setActiveOrderIndex(errorQuestionList[activeIndex + 1].orderIndex);
setActiveIndex(activeIndex + 1);
}
} else {
if (isPre && activeOrderIndex !== 0) {
setActiveOrderIndex(activeIndex - 1);
setActiveIndex(activeIndex - 1);
} else if (!isPre && activeOrderIndex !== questionList.length - 1) {
setActiveOrderIndex(activeIndex + 1);
setActiveIndex(activeIndex + 1);
}
}
}
function renderFooterText() {
if (onlyError && errorCount > 0) {
// 只看错题
return (
<div className='footer-btn'>
<div className='pre-next'>
<div
className={`${activeOrderIndex === (errorQuestionList.length > 0 ? errorQuestionList[0].orderIndex : 0) ? 'disabled' : ''} pre`}
onClick={() => handleChangeActiveIndex(true)}>
<span className='icon iconfont'>&#xe79c;</span>
<div className='text'>上一题</div>
</div>
<div
className={`${activeOrderIndex === (errorQuestionList.length > 0 ? errorQuestionList[errorCount - 1].orderIndex : 0) ? 'disabled' : ''} next`}
onClick={() => handleChangeActiveIndex(false)}>
<div className='text'>下一题</div>
<span className='icon iconfont'>&#xe79b;</span>
</div>
</div>
</div>
);
} else if (!onlyError) {
return (
<div className='footer-btn'>
<div className='pre-next'>
<div className={`${activeOrderIndex === 0 ? 'disabled' : ''} pre`} onClick={() => handleChangeActiveIndex(true)}>
<span className='icon iconfont'>&#xe79c;</span>
<div className='text'>上一题</div>
</div>
<div className={`${activeOrderIndex === questionList.length - 1 ? 'disabled' : ''} next`} onClick={() => handleChangeActiveIndex(false)}>
<div className='text'>下一题</div>
<span className='icon iconfont'>&#xe79b;</span>
</div>
</div>
</div>
);
}
}
function handleRenderQuestionItem() {
return _.map(questionList, (questionItem, questionIndex) => {
const { questionStemList, optionList, gapFillingAnswerList, questionAnswerDescList, questionType, score, questionId, orderIndex } = questionItem;
return (
<div className={`question-info-item`}>
{renderStem(questionItem, questionStemList, questionType, score, orderIndex, questionId, gapFillingAnswerList)}
{questionType !== 'GAP_FILLING' &&
_.map(optionList, (optionItem, optionIndex) => {
return renderOption(optionItem, optionIndex, questionId);
})}
{renderAnswerCompare(questionId, questionType, optionList, gapFillingAnswerList)}
{renderAnswerDesc(questionAnswerDescList)}
</div>
);
});
}
// 查看图片或视频
function handleScanFile(scanFileType, scanFileAddress) {
setShowScanFile(true);
setScanFileAddress(scanFileAddress);
setScanFileType(scanFileType);
}
// 渲染多媒体内容
function renderMediaContent(mediaContent) {
return (
<div className='media-container'>
{_.map(mediaContent, (mediaItem, mediaIndex) => {
let dom = '';
let { type, content, size } = mediaItem;
switch (type) {
case 'PICTURE':
dom = (
<div key={mediaIndex + 1} className='picture-box'>
<img src={content} onClick={() => handleScanFile('JPG', content)} />
</div>
);
break;
case 'VOICE':
dom = (
<div key={mediaIndex + 1} className='voice-box'>
<XMAudio
url={content}
getDuration={(durationSize) => {
size = durationSize;
}}
index={mediaIndex + 1}
size={size || 1000}
/>
</div>
);
break;
case 'AUDIO':
dom = (
<div key={mediaIndex} className='voice-box'>
<XMAudio
url={content}
getDuration={(durationSize) => {
size = durationSize;
}}
index={mediaIndex}
size={size || 1000}
/>
</div>
);
break;
}
return dom;
})}
</div>
);
}
// 渲染题干
function renderStem(questionItem, questionStemList, questionType, score, orderIndex, questionId, gapFillingAnswerList) {
const textContent = _.filter(questionStemList, (item) => {
return item.type === 'RICH_TEXT';
});
const mediaContent = _.filter(questionStemList, (item) => {
return item.type !== 'RICH_TEXT';
});
let content = textContent.length > 0 ? textContent[0].content : '';
const userAnswerItem = _.filter(userAnswerList, (item) => {
return item.questionId === questionId;
});
// 填空题题干渲染
if (questionType === 'GAP_FILLING' && userAnswerItem.length > 0) {
let userAnswer = [];
if (userAnswerItem[0].answer) {
userAnswer = userAnswerItem[0].answer;
} else {
gapFillingAnswerList.forEach((item, index) => {
userAnswer.push('');
});
}
gapFillingAnswerList.map((gapItem, gapIndex) => {
let gapValue = gapItem.correctAnswerList.includes(userAnswer[gapIndex])
? `<img src="https://image.xiaomaiketang.com/xm/FwZa2Kaypc.png" />`
: `<img src="https://image.xiaomaiketang.com/xm/7tRHDf6ysA.png" />`;
content = content.replace(
/(_)|(<input.*?>)/,
`<div class="gap-line"><span>${userAnswer[gapIndex] === '@X#$' ? '' : userAnswer[gapIndex]} </span>${gapValue}</div>`
);
});
}
let textDom = (
<div
key={0}
className='text-dom'
dangerouslySetInnerHTML={{
__html: content,
}}
/>
);
return (
<div className='stem-line__item' id={questionItem.questionId}>
<div className='text'>
<img
className='answer-icon'
src={userAnswerItem[0].isCorrect === 1 ? 'https://image.xiaomaiketang.com/xm/FwZa2Kaypc.png' : 'https://image.xiaomaiketang.com/xm/7tRHDf6ysA.png'}
/>
<span className='question-index'>{orderIndex + 1}</span>
<span className='steam-line_type'>{`${questionTypeList[questionType]} | ${score}分`}</span>
{textDom}
</div>
{renderMediaContent(mediaContent)}
</div>
);
}
// 渲染选项
function renderOption(optionItem, optionIndex, questionId) {
const { questionOptionContentList, optionSort, isCorrectAnswer, id } = optionItem;
const textContent = _.filter(questionOptionContentList, (item) => {
return item.type === 'RICH_TEXT';
});
const mediaContent = _.filter(questionOptionContentList, (item) => {
return item.type !== 'RICH_TEXT';
});
let content = textContent.length > 0 ? textContent[0].content : '';
let textDom = (
<span
key={0}
className='text-dom'
dangerouslySetInnerHTML={{
__html: content,
}}
/>
);
const userAnswerItem = _.filter(userAnswerList, (item) => {
return item.questionId === questionId;
});
let userAnswer = [];
if (userAnswerItem.length > 0) {
if (userAnswerItem[0].answer) {
userAnswer = userAnswerItem[0].answer;
}
}
let optionStatus = '';
if (userAnswer.includes(id)) {
// 选中
optionStatus = 'answered';
}
let answerStatusIcon = 'default';
if (isCorrectAnswer) {
answerStatusIcon = 'https://image.xiaomaiketang.com/xm/FwZa2Kaypc.png';
} else if (userAnswer.includes(id)) {
answerStatusIcon = 'https://image.xiaomaiketang.com/xm/7tRHDf6ysA.png';
} else {
answerStatusIcon = 'default';
}
return (
<div className='option-line__item'>
<div className={`text ${optionStatus}`}>
<div className='option-sort'>{NUM_TO_WORD_MAP[optionSort]}</div>
<div className='option-content'>
<div className='text-dom'>{textDom}</div>
{mediaContent.length > 0 && renderMediaContent(mediaContent)}
</div>
</div>
{answerStatusIcon !== 'default' && <img className='icon' src={answerStatusIcon} />}
</div>
);
}
// 渲染答案对比
function renderAnswerCompare(questionId, questionType, optionList, gapFillingAnswerList) {
const userAnswerItem = _.filter(userAnswerList, (item) => {
return item.questionId === questionId;
});
const userAnswer = userAnswerItem.length > 0 && userAnswerItem[0].answer;
if (questionType === 'GAP_FILLING') {
return (
<div className='answer-compare-box'>
<div className='answer-info' style={{ marginBottom: '0.16rem' }}>
<div className='title'>考试作答是</div>
{userAnswer &&
userAnswer.map((item, index) => {
return (
<div className='content'>
<span>[填空{index + 1}]</span>
<span
dangerouslySetInnerHTML={{
__html: item === '@X#$' ? '' : item,
}}
/>
<span>;</span>
</div>
);
})}
</div>
<div className='answer-info'>
<div className='title'>正确答案是</div>
{gapFillingAnswerList.map((item, index) => {
return (
<div className='content' style={{ color: '#20CECD' }}>
<span>[填空{index + 1}]</span>
{_.map(item.correctAnswerList, (childItem, childIndex) => {
return (
<span
dangerouslySetInnerHTML={{
__html: childItem,
}}
/>
);
})}
<span>;</span>
</div>
);
})}
</div>
</div>
);
} else {
// 正确答案序号
const rightOptionSort = _.filter(optionList, (item) => {
return item.isCorrectAnswer === 1;
}).map((sortItem) => {
return sortItem.optionSort;
});
const userAnswerSort = _.filter(optionList, (item) => {
if (userAnswer) {
return userAnswer.includes(item.id);
}
}).map((sortItem) => {
return sortItem.optionSort;
});
return (
<div className='answer-compare-box'>
<div className='answer-info'>
<div className='title'>考试作答是</div>
<div className='content'>
{userAnswerSort.map((item) => {
return <span>{NUM_TO_WORD_MAP[item]} </span>;
})}
</div>
</div>
<div className='answer-info'>
<div className='title'>正确答案是</div>
<div className='content' style={{ color: '#20CECD' }}>
{rightOptionSort.map((item) => {
return <span>{NUM_TO_WORD_MAP[item]} </span>;
})}
</div>
</div>
</div>
);
}
}
// 渲染答案解析
function renderAnswerDesc(questionAnswerDescList) {
const textContent = _.filter(questionAnswerDescList, (item) => {
return item.type === 'RICH_TEXT';
});
const mediaContent = _.filter(questionAnswerDescList, (item) => {
return item.type !== 'RICH_TEXT';
});
let content = textContent.length > 0 ? `${textContent[0].content}:` : '';
let textDom = (
<div
key={0}
className='text-dom'
dangerouslySetInnerHTML={{
__html: content,
}}
/>
);
return (
<div className='desc-line__item'>
{textDom}
{renderAnswerDescMedia(mediaContent)}
</div>
);
}
// 渲染答案解析的多媒体
function renderAnswerDescMedia(mediaContent) {
const pictureMediaList = _.filter(mediaContent, (mediaItem) => {
return mediaItem.type === 'PICTURE';
});
const voiceMediaList = _.filter(mediaContent, (mediaItem) => {
return mediaItem.type === 'VOICE';
});
const audioMediaList = _.filter(mediaContent, (mediaItem) => {
return mediaItem.type === 'AUDIO';
});
const videoMediaList = _.filter(mediaContent, (mediaItem) => {
return mediaItem.type === 'VIDEO';
});
return (
<div className='desc-media-container'>
{pictureMediaList.length > 0 && (
<div className='desc-picture-box'>
{_.map(pictureMediaList, (pictureItem, pictureIndex) => {
let { content } = pictureItem;
return (
<div className='picture-box' key={pictureIndex}>
<img className='img-box' src={content} onClick={() => handleScanFile('JPG', content)} />
</div>
);
})}
</div>
)}
{audioMediaList.length > 0 && (
<div className='desc-audio-box'>
{_.map(audioMediaList, (audioItem, audioIndex) => {
let { content, size } = audioItem;
return (
<div className='audio-box' key={audioIndex}>
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
}}
index={audioIndex}
size={size || 1000}
/>
</div>
);
})}
</div>
)}
{voiceMediaList.length > 0 && (
<div className='desc-audio-box'>
{_.map(voiceMediaList, (voiceItem, voiceIndex) => {
let { content, size } = voiceItem;
return (
<div className='audio-box' key={voiceIndex}>
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
}}
index={voiceIndex}
size={size || 1000}
/>
</div>
);
})}
</div>
)}
{videoMediaList.length > 0 && (
<div className='desc-video-box'>
{_.map(videoMediaList, (videoItem, videoIndex) => {
let { content } = videoItem;
return (
<div className='video-box' key={videoIndex}>
<img className='video-box_content' src={`${content}?x-oss-process=video/snapshot,t_0,m_fast`} />
<img className='video-box_btn' src='https://image.xiaomaiketang.com/xm/r5H8cYm4ch.png' onClick={() => handleScanFile('MP4', content)} />
</div>
);
})}
</div>
)}
</div>
);
}
// 答题卡展开和收起
function handleToggleQuestionCardShow() {
setIsShowQuestionCard(!isShowQuestionCard);
}
// 快速跳转题目
function handleQuickActiveQuestion(orderIndex, answerIndex) {
setActiveOrderIndex(orderIndex);
setActiveIndex(answerIndex);
setIsShowQuestionCard(false);
}
// 只选错题
function chooseErrorAnswer() {
setOnlyError(!onlyError);
setActiveOrderIndex(!onlyError ? (errorCount > 0 ? errorQuestionList[0].orderIndex : 0) : 0);
setActiveIndex(0);
setUserAnswerList(!onlyError ? errorUserAnswerList : allUserAnswerList);
setQuestionList(!onlyError ? errorQuestionList : allQuestionList);
}
const { totalQuestionCount, userCorrectQuestion } = examDetail;
let sortedAnswerList = [];
let userAnswerMap = {};
userAnswerList.forEach((item) => {
userAnswerMap[item.questionId] = item;
});
questionList.forEach((item) => {
if (userAnswerMap[item.questionId]) {
sortedAnswerList.push({
...userAnswerMap[item.questionId],
orderIndex: item.orderIndex,
});
}
});
return (
<div className='answer-desc-page'>
<div className='center'>
<div className='box'>
<div className='box-content'>
<div className='answer-desc-header'>
<div className='desc-header-container'>
<div className='desc-title'>答案解析</div>
<div className='choose-error-box'>
<img
onClick={() => {
chooseErrorAnswer();
}}
src={onlyError ? 'https://image.xiaomaiketang.com/xm/FwZa2Kaypc.png' : 'https://image.xiaomaiketang.com/xm/crtyKFjcAm.png'}
/>
<span>只看错题</span>
</div>
</div>
</div>
</div>
{errorCount === 0 && onlyError ? (
<div className='empty'>
<div className='img-box'>
<img src={'https://image.xiaomaiketang.com/xm/sz3CMdQKQE.png'} alt='' />
</div>
<div className='empty-text'>本次考试无错题</div>
</div>
) : (
<div className='answer-desc-content'>
<div className='question-list-box'>{handleRenderQuestionItem()}</div>
</div>
)}
{/* {renderFooterText()} */}
</div>
</div>
{/* {showScanFile && (
<ScanFileModal
fileType={scanFileType}
item={{
ossAddress: scanFileAddress,
}}
close={() => {
setShowScanFile(false);
}}
/>
)} */}
</div>
);
}
export default withRouter(AnswerDescPage);
.answer-desc-page {
background-color: #fff;
p {
margin: 0 !important;
}
.answer-desc-header {
.desc-header-container {
display: flex;
justify-content: space-between;
align-items: center;
.desc-title {
font-size: 20px;
font-weight: 500;
color: #333333;
position: relative;
margin-left: 8px;
line-height: 28px;
&::before {
position: absolute;
content: '';
left: -8px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: #336dff;
}
}
.choose-error-box {
display: flex;
align-items: center;
img {
width: 18px;
height: 18px;
margin-right: 2px;
}
span {
font-size: 15px;
color: #999999;
}
}
}
}
.answer-desc-content {
width: 840px;
margin: 0 auto;
text-align: left;
.question-list-box {
padding: 21px 0;
.question-info-item {
.stem-line__item {
margin-bottom: 16px;
.text {
line-height: 1.8;
.answer-icon {
width: 20px;
height: 20px;
margin-right: 12px;
display: inline-block;
margin-bottom: 5px;
}
.question-index {
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
}
.steam-line_type {
font-size: 13px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
}
.text-dom {
color: #333333;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
font-size: 17px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
* {
display: inline;
}
.gap-line {
border-bottom: 1px solid;
margin: 0 8px;
padding: 0 4px;
img {
margin-left: 4px;
width: 16px;
height: 16px;
}
}
}
}
}
.option-line__item {
margin-bottom: 12px;
display: flex;
align-items: center;
.icon {
margin-left: 12px;
width: 20px;
height: 20px;
}
.text {
width: 100%;
background: #f4f6fa;
border-radius: 5px;
display: flex;
align-items: center;
padding: 16px;
.option-sort {
font-size: 15px;
line-height: 21px;
position: relative;
&::after {
position: absolute;
left: 22px;
content: '';
width: 1px;
height: 20px;
background-color: rgba(238, 238, 238, 1);
}
}
.option-content {
margin-left: 32px;
}
.text-dom {
font-size: 15px;
color: #333333;
line-height: 21px;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
}
&.answered {
background: rgba(41, 102, 255, 0.05);
border: 1px solid #2966ff;
color: #2966ff;
.text-dom {
color: #2966ff;
}
}
&.right {
background: rgba(21, 217, 177, 0.05);
border: 1px solid #15d9b1;
color: #15d9b1;
}
&.wrong {
background: rgba(255, 79, 79, 0.05);
border: 1px solid #ff4f4f;
color: #ff4f4f;
}
}
}
.desc-line__item {
margin-top: 20px;
.desc-title {
font-size: 20px;
font-weight: 500;
color: #333333;
position: relative;
margin-left: 8px;
line-height: 36px;
&::before {
position: absolute;
content: '';
left: -8px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: #336dff;
}
}
.text-dom {
font-size: 14px;
color: #999999;
line-height: 20px;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
* {
display: inline;
}
}
}
.media-container {
.picture-box {
width: 88px;
height: 88px;
border-radius: 4px;
overflow: hidden;
align-items: center;
justify-content: center;
margin-top: 12px;
margin-right: 12px;
position: relative;
display: inline-flex;
border: 1px solid #e8e8e8;
img {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
vertical-align: middle;
width: auto;
height: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.voice-box {
width: 27px;
height: 40px;
margin-top: 12px;
margin-right: 12px;
}
}
.desc-media-container {
margin: 12px 0;
.desc-picture-box {
display: inline-flex;
margin-bottom: 12px;
.picture-box {
width: 88px;
height: 88px;
border-radius: 4px;
overflow: hidden;
align-items: center;
justify-content: center;
margin-right: 12px;
position: relative;
display: inline-flex;
border: 1px solid #e8e8e8;
.img-box {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
vertical-align: middle;
width: auto;
height: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
.desc-audio-box {
margin-bottom: 12px;
.audio-box {
margin-bottom: 12px;
border-radius: 5px;
height: 40px;
}
}
.desc-video-box {
.video-box {
position: relative;
display: inline-block;
width: 208px;
height: calc(208px * 9 / 16);
position: relative;
background-color: #000;
margin: 0px 12px 12px 0;
&_content {
max-width: 100%;
max-height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&_btn {
width: 32px;
height: 32px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -16px;
margin-left: -16px;
}
.icon_arrow {
position: absolute;
top: -12px;
right: -8px;
color: #bfbfbf;
cursor: pointer;
font-size: 16px;
}
}
}
}
.answer-compare-box {
margin-top: 16px;
padding-top: 12px;
padding-bottom: 12px;
font-size: 15px;
.answer-info {
margin-bottom: 8px;
.title {
color: #999999;
display: inline-block;
margin-right: 12px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 20px;
}
.content {
font-weight: 500;
color: #333333;
display: inline;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
span {
margin-right: 4px;
}
}
}
}
}
}
}
.footer-btn {
width: 100%;
height: 64px;
background: #ffffff;
position: fixed;
bottom: 0;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid rgba(238, 238, 238, 1);
padding: 10px 16px;
.hand-in-paper {
display: flex;
.icon {
width: 15px;
line-height: 18px;
color: rgba(255, 183, 20, 1);
margin-right: 6px;
}
}
.pre-next {
width: 343px;
height: 44px;
border-radius: 5px;
border: 2px solid #eeeeee;
display: flex;
justify-content: space-between;
.pre,
.next {
width: 50%;
display: flex;
align-items: center;
justify-content: center;
.icon {
width: 17px;
line-height: 11px;
margin-left: 5px;
color: #ccc;
}
.text {
font-size: 15px;
color: #666;
line-height: 21px;
}
&.disabled {
pointer-events: none;
.text {
color: #cccccc;
}
}
}
}
}
}
.empty {
z-index: 10;
width: 100%;
height: 360px;
background-color: #fff;
.img-box {
margin: 0 auto;
width: 120px;
}
img {
height: 120px;
display: inline-block;
margin-top: 100px;
margin-bottom: 18px;
}
.empty-text {
width: 250px;
height: 18px;
font-size: 13px;
color: #333;
line-height: 18px;
margin: 0 auto;
text-align: center;
}
}
import React, { useState, useRef, useEffect, useContext } from 'react'
import React, { useState, useRef, useEffect, useContext } from 'react';
import { Input, Select, DatePicker, Tooltip, Button, Table, Dropdown, Menu, Modal } from 'antd';
import TeacherSelect from '@/modules/common/TeacherSelect';
import { Route, withRouter } from 'react-router-dom';
import Service from "@/common/js/service";
import Service from '@/common/js/service';
import moment from 'moment';
import { PageControl } from "@/components";
import { PageControl } from '@/components';
import AddExam from './AddExam';
import User from "@/common/js/user";
import { XMContext } from "@/store/context";
import ExamShareModal from './ExamShareModal'
import DataAnalysic from './DataAnalysic'
import './index.less'
import User from '@/common/js/user';
import { XMContext } from '@/store/context';
import ExamShareModal from './ExamShareModal';
import DataAnalysic from './DataAnalysic';
import './index.less';
const { RangePicker } = DatePicker;
const { Search } = Input;
const { Option } = Select;
interface sortType {
type: "ascend" | "descend" | null | undefined
type: 'ascend' | 'descend' | null | undefined;
}
interface fixType {
left :boolean | "right" | "left" | undefined,
right: "right" | "left" ,
left: boolean | 'right' | 'left' | undefined;
right: 'right' | 'left';
}
const fixStr:fixType={
left:'left',
right:'right'
}
const fixStr: fixType = {
left: 'left',
right: 'right',
};
function ExaminationManager(props: any) {
const queryInit: any = {
examName: '',
current: 1, size: 10, order: 'EXAM_START_TIME_DESC'
}
const sortStatus: sortType = {
type: undefined
}
const sortEnum = {
}
const { match } = props;
const sortState: any = false;
const ctx: any = useContext(XMContext);
const [query, setQuery] = useState(queryInit);
const [expandFilter, setExpandFilter] = useState(false);
const [total, setTotal] = useState(0);
const [list, setList] = useState([]);
const [field, setfield] = useState('');
const [order, setOrder] = useState(sortStatus.type);
const [modal, setModal] = useState(null);
const [questionCntSort, setQuestionCntSort] = useState(sortState)
const queryRef = useRef({});
const orderEnum = {
userCnt: {
ascend: 'USER_CNT_ASC',
descend: 'USER_CNT_DESC'
},
passCnt: {
ascend: 'PASS_CNT_ASC',
descend: 'PASS_CNT_DESC'
},
examCreateTime: {
ascend: 'EXAM_START_TIME_ASC',
descend: 'EXAM_START_TIME_DESC'
const queryInit: any = {
examName: '',
current: 1,
size: 10,
order: 'EXAM_START_TIME_DESC',
};
const sortStatus: sortType = {
type: undefined,
};
const sortEnum = {};
const { match } = props;
const sortState: any = false;
const ctx: any = useContext(XMContext);
const [query, setQuery] = useState(queryInit);
const [expandFilter, setExpandFilter] = useState(false);
const [total, setTotal] = useState(0);
const [list, setList] = useState([]);
const [field, setfield] = useState('');
const [order, setOrder] = useState(sortStatus.type);
const [modal, setModal] = useState(null);
const [questionCntSort, setQuestionCntSort] = useState(sortState);
const queryRef = useRef({});
const orderEnum = {
userCnt: {
ascend: 'USER_CNT_ASC',
descend: 'USER_CNT_DESC',
},
passCnt: {
ascend: 'PASS_CNT_ASC',
descend: 'PASS_CNT_DESC',
},
examCreateTime: {
ascend: 'EXAM_START_TIME_ASC',
descend: 'EXAM_START_TIME_DESC',
},
};
const columns = [
{
title: '考试',
// fixed:fixStr.left,
width: 320,
dataIndex: 'examName',
render: (text: any, record: any) => {
var _text = '未开始',
_color = 'rgba(255, 183, 20, 1)';
if (moment().valueOf() > record.examEndTime) {
_text = '已结束';
_color = 'rgba(153, 153, 153, 1)';
} else if (moment().valueOf() > record.examStartTime) {
_text = '进行中';
_color = 'rgba(59, 189, 170, 1)';
}
}
const columns = [
{
title: "考试",
// fixed:fixStr.left,
width:320,
dataIndex: "examName",
render: (text: any, record: any) => {
var _text = '未开始', _color = 'rgba(255, 183, 20, 1)';
if (moment().valueOf() > record.examEndTime) {
_text = '已结束';
_color = 'rgba(153, 153, 153, 1)';
} else if (moment().valueOf() > record.examStartTime) {
_text = '进行中';
_color = 'rgba(59, 189, 170, 1)';
}
return <div style={{ width: 320 }}>
<div className='oneLineText' style={{ width: 320,color:'#333',fontWeight:'bold' }} >{text}</div>
<div> <span >{moment(record.examStartTime).format("YYYY-MM-DD HH:mm")}~{moment(record.examEndTime).format("YYYY-MM-DD HH:mm")} </span> <div className="status" style={{ border: `1px solid ${_color}`,borderRadius:2, color: _color }}>{_text}</div></div>
<div>创建人:{record.examCreator}</div>
</div>
},
},
{
title: "考试时长",
dataIndex: "examDuration",
render: (text: any) => <span>{(text || 0) / 60 / 1000}分钟</span>,
},
{
title: "及格分/总分",
dataIndex: "totalScore",
render: (text: any, record: any) => <span>{`${record.passScore || 0}/${record.totalScore || 0}`}</span>,
},
{
title: "题目数量",
align:fixStr.right,
dataIndex: "questionCnt",
},
{
title: "参与人数",
dataIndex: "userCnt",
align:fixStr.right,
sorter: true,
sortOrder: field === "userCnt" ? order : sortStatus.type,
},
{
title: "及格数",
dataIndex: "passCnt",
align:fixStr.right,
sorter: true,
sortOrder: field === "passCnt" ? order : sortStatus.type,
},
{
title: "创建时间",
dataIndex: "examCreateTime",
align:fixStr.right,
sorter: true,
sortOrder: field === "examCreateTime" ? order : sortStatus.type,
render: (text: any, record: any) => <span>{moment(text).format("YYYY-MM-DD HH:mm")}</span>,
},
{
title: "操作",
fixed:fixStr.right,
dataIndex: "operate",
render: (text: any, record: any) => <div className="table_operate">
{
ctx.xmState?.userPermission?.SeeExamData() && [<div
key="data"
className="operate__item"
onClick={() => {
props.history.push({
pathname: `${match.url}/analysic/${record.examId}`
})
}}
>
数据
</div>,
<span className="operate__item split" > | </span>]
}
<div
key="share"
className="operate__item"
onClick={() => { shareModal(record) }}
>
分享
</div>
{
((ctx.xmState?.userPermission?.AddExam() && (moment().valueOf() < record.examStartTime)) || (ctx.xmState?.userPermission?.DelExam() && (moment().valueOf() + 30 * 60 * 1000 < record.examStartTime))) && [<span className="operate__item split" > | </span>, <Dropdown overlay={getOpe(record)}>
<span className='more'>更多</span>
</Dropdown>]
}
return (
<div style={{ width: 320 }}>
<div className='oneLineText' style={{ width: 320, color: '#333', fontWeight: 'bold' }}>
{text}
</div>
<div>
{' '}
<span>
{moment(record.examStartTime).format('YYYY-MM-DD HH:mm')}~{moment(record.examEndTime).format('YYYY-MM-DD HH:mm')}{' '}
</span>{' '}
<div className='status' style={{ border: `1px solid ${_color}`, borderRadius: 2, color: _color }}>
{_text}
</div>
</div>
<div>创建人:{record.examCreator}</div>
</div>
);
},
},
{
title: '考试时长',
dataIndex: 'examDuration',
render: (text: any) => <span>{(text || 0) / 60 / 1000}分钟</span>,
},
{
title: '及格分/总分',
dataIndex: 'totalScore',
render: (text: any, record: any) => <span>{`${record.passScore || 0}/${record.totalScore || 0}`}</span>,
},
{
title: '题目数量',
align: fixStr.right,
dataIndex: 'questionCnt',
},
{
title: '参与人数',
dataIndex: 'userCnt',
align: fixStr.right,
sorter: true,
sortOrder: field === 'userCnt' ? order : sortStatus.type,
},
{
title: '及格数',
dataIndex: 'passCnt',
align: fixStr.right,
sorter: true,
sortOrder: field === 'passCnt' ? order : sortStatus.type,
},
{
title: '创建时间',
dataIndex: 'examCreateTime',
align: fixStr.right,
sorter: true,
sortOrder: field === 'examCreateTime' ? order : sortStatus.type,
render: (text: any, record: any) => <span>{moment(text).format('YYYY-MM-DD HH:mm')}</span>,
},
{
title: '操作',
fixed: fixStr.right,
dataIndex: 'operate',
render: (text: any, record: any) => (
<div className='table_operate'>
{ctx.xmState?.userPermission?.SeeExamData() && [
<div
key='data'
className='operate__item'
onClick={() => {
props.history.push({
pathname: `${match.url}/analysic/${record.examId}`,
});
}}>
数据
</div>,
},
];
function shareModal(record: any) {
const modal = <ExamShareModal
data={record}
close={() => {
setModal(null)
}}
/>
setModal(modal as any)
}
function getOpe(item: any) {
return <Menu>
{
ctx.xmState?.userPermission?.AddExam() && (moment().valueOf() < item.examStartTime) && <Menu.Item key="0">
<span
onClick={() => {
if (moment().valueOf() + 5 * 60 * 1000 > item.examStartTime) {
Modal.info({
title: '无法编辑',
content: '离考试开始时间小于5分钟,为保证答题数据的准确性,不能再进行编辑了',
})
} else {
props.history.push({
pathname: `${match.url}/edit/${item.examId}`
})
}
}}
>
编辑
</span>
</Menu.Item>
}
{
ctx.xmState?.userPermission?.DelExam() && (moment().valueOf() + 30 * 60 * 1000 < item.examStartTime) && <Menu.Item key="1">
<span
onClick={() => {
deleteExam(item)
}}
>
删除
</span>
</Menu.Item>
}
</Menu>
}
function deleteExam(item: any) {
Modal.confirm({
title: '删除考试',
content: '确定删除该考试吗?',
okText: '删除',
cancelText: '取消',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
Service.Hades("public/hades/deleteExam", {
"examId": item.examId,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0
}).then(() => {
getList()
})
}
})
}
function getList() {
const _query = { ...queryRef.current };
// if(_query.examCreator){
// _query.examCreator =parseInt(_query.examCreator)
// }
Service.Hades("public/hades/queryExamPageList", {
..._query, userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0
}).then((res) => {
setList(res.result?.records || [])
setTotal(parseInt(res.result.total))
})
}
useEffect(() => {
queryRef.current = query;
getList();
}, [query])
function onShowSizeChange(current: any, size: any) {
( queryRef.current as any).size =size
}
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] || 'EXAM_START_TIME_DESC'
setQuery(_query)
}
return <div className="page examination-manager">
<div className="content-header">考试</div>
<div className="box content-body">
<div className="xm-search-filter">
<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.examName = e.target.value
_query.current = 1;
setQuery(_query);
}}
onSearch={() => { }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<TeacherSelect val={query.examCreator}
onChange={(examCreator: any) => {
const _query = { ...query }
_query.examCreator = examCreator;
_query.current = 1;
setQuery(_query);
}}
roleCodes={["CloudManager", 'StoreManager']}
></TeacherSelect>
<div className="search-condition__item">
<span className="search-name">考试时间:</span>
<RangePicker
className='search-input'
value={[
query.examStartTime ? moment(Number(query.examStartTime)) : null,
query.examStartTime ? moment(Number(query.examEndTime)) : null
]}
onChange={(date: any) => {
const _query = { ...query }
_query.examStartTime = date && date[0]?.startOf('day').valueOf();
_query.examEndTime = date && date[1]?.endOf('day').valueOf();
_query.current = 1;
setQuery(_query);
}} />
</div>
{
!!expandFilter && <div className="search-condition__item">
<span className="search-name">创建时间:</span>
<RangePicker
className='search-input'
value={[
query.createStartTime ? moment(Number(query.createStartTime)) : null,
query.createStartTime ? moment(Number(query.createEndTime)) : null
]}
onChange={(date: any) => {
const _query = { ...query }
_query.createStartTime = date && date[0]?.startOf('day').valueOf();
_query.createEndTime = date && date[1]?.endOf('day').valueOf();
_query.current = 1;
setQuery(_query);
}} />
</div>
}
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={() => {
setfield('')
setQuery({ current: 1, size: 10, order: 'EXAM_START_TIME_DESC' });
}}>&#xe61b; </span></Tooltip>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => {
setExpandFilter(!expandFilter)
}}>{expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span>
</div>
<span className='operate__item split' key={'split-1'}>
{' '}
|{' '}
</span>,
]}
<div
key='share'
className='operate__item'
onClick={() => {
shareModal(record);
}}>
分享
</div>
{((ctx.xmState?.userPermission?.AddExam() && moment().valueOf() < record.examStartTime) ||
(ctx.xmState?.userPermission?.DelExam() && moment().valueOf() + 30 * 60 * 1000 < record.examStartTime)) && [
<span className='operate__item split' key={'split-2'}>
{' '}
|{' '}
</span>,
<Dropdown overlay={getOpe(record)}>
<span className='more'>更多</span>
</Dropdown>,
]}
</div>
),
},
];
function shareModal(record: any) {
const modal = (
<ExamShareModal
data={record}
close={() => {
setModal(null);
}}
/>
);
setModal(modal as any);
}
function getOpe(item: any) {
return (
<Menu>
{ctx.xmState?.userPermission?.AddExam() && moment().valueOf() < item.examStartTime && (
<Menu.Item key='0'>
<span
onClick={() => {
if (moment().valueOf() + 5 * 60 * 1000 > item.examStartTime) {
Modal.info({
title: '无法编辑',
content: '离考试开始时间小于5分钟,为保证答题数据的准确性,不能再进行编辑了',
});
} else {
props.history.push({
pathname: `${match.url}/edit/${item.examId}`,
});
}
}}>
编辑
</span>
</Menu.Item>
)}
{ctx.xmState?.userPermission?.DelExam() && moment().valueOf() + 30 * 60 * 1000 < item.examStartTime && (
<Menu.Item key='1'>
<span
onClick={() => {
deleteExam(item);
}}>
删除
</span>
</Menu.Item>
)}
</Menu>
);
}
function deleteExam(item: any) {
Modal.confirm({
title: '删除考试',
content: '确定删除该考试吗?',
okText: '删除',
cancelText: '取消',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
Service.Hades('public/hades/deleteExam', {
examId: item.examId,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0,
}).then(() => {
getList();
});
},
});
}
function getList() {
const _query = { ...queryRef.current };
// if(_query.examCreator){
// _query.examCreator =parseInt(_query.examCreator)
// }
Service.Hades('public/hades/queryExamPageList', {
..._query,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0,
}).then((res) => {
setList(res.result?.records || []);
setTotal(parseInt(res.result.total));
});
}
useEffect(() => {
queryRef.current = query;
getList();
}, [query]);
function onShowSizeChange(current: any, size: any) {
(queryRef.current as any).size = size;
}
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] || 'EXAM_START_TIME_DESC';
setQuery(_query);
}
return (
<div className='page examination-manager'>
<div className='content-header'>考试</div>
<div className='box content-body'>
<div className='xm-search-filter'>
<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.examName = e.target.value;
_query.current = 1;
setQuery(_query);
}}
onSearch={() => {}}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
</div>
<TeacherSelect
val={query.examCreator}
onChange={(examCreator: any) => {
const _query = { ...query };
_query.examCreator = examCreator;
_query.current = 1;
setQuery(_query);
}}
roleCodes={['CloudManager', 'StoreManager']}></TeacherSelect>
<div className='search-condition__item'>
<span className='search-name'>考试时间:</span>
<RangePicker
className='search-input'
value={[query.examStartTime ? moment(Number(query.examStartTime)) : null, query.examStartTime ? moment(Number(query.examEndTime)) : null]}
onChange={(date: any) => {
const _query = { ...query };
_query.examStartTime = date && date[0]?.startOf('day').valueOf();
_query.examEndTime = date && date[1]?.endOf('day').valueOf();
_query.current = 1;
setQuery(_query);
}}
/>
</div>
{!!expandFilter && (
<div className='search-condition__item'>
<span className='search-name'>创建时间:</span>
<RangePicker
className='search-input'
value={[
query.createStartTime ? moment(Number(query.createStartTime)) : null,
query.createStartTime ? moment(Number(query.createEndTime)) : null,
]}
onChange={(date: any) => {
const _query = { ...query };
_query.createStartTime = date && date[0]?.startOf('day').valueOf();
_query.createEndTime = date && date[1]?.endOf('day').valueOf();
_query.current = 1;
setQuery(_query);
}}
/>
</div>
)}
</div>
{
ctx.xmState?.userPermission?.AddExam() && <Button type='primary' onClick={() => {
props.history.push({
pathname: `${match.url}/add`
})
}} style={{ margin: '4px 0 16px' }}>新建考试</Button>
}
<div className="content">
<Table
bordered
size="small"
columns={columns}
dataSource={list}
scroll={{ x: 1150 }}
onChange={onChange}
pagination={false}
style={{ margin: '0px 0 16px' }}
>
</Table>
{total > 0 &&
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
onShowSizeChange={onShowSizeChange}
toPage={(page: any) => {
console.log(page)
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query)
}}
/>
}
<div className='reset-fold-area'>
<Tooltip title='清空筛选'>
<span
className='resetBtn iconfont icon'
onClick={() => {
setfield('');
setQuery({ current: 1, size: 10, order: 'EXAM_START_TIME_DESC' });
}}>
&#xe61b;{' '}
</span>
</Tooltip>
<span
style={{ cursor: 'pointer' }}
className='fold-btn'
onClick={() => {
setExpandFilter(!expandFilter);
}}>
{expandFilter ? (
<span>
<span>收起</span>
<span className='iconfont icon fold-icon'>&#xe82d; </span>{' '}
</span>
) : (
<span>
展开<span className='iconfont icon fold-icon'>&#xe835; </span>
</span>
)}
</span>
</div>
</div>
</div>
<Route path={`${match.url}/add`} render={() => {
return <AddExam freshList={() => {
{ctx.xmState?.userPermission?.AddExam() && (
<Button
type='primary'
onClick={() => {
props.history.push({
pathname: `${match.url}/add`,
});
}}
style={{ margin: '4px 0 16px' }}>
新建考试
</Button>
)}
<div className='content'>
<Table
bordered
size='small'
columns={columns}
dataSource={list}
scroll={{ x: 1150 }}
onChange={onChange}
pagination={false}
rowKey={(record) => record.examId}
style={{ margin: '0px 0 16px' }}></Table>
{total > 0 && (
<PageControl
size='small'
current={query.current - 1}
pageSize={query.size}
total={total}
onShowSizeChange={onShowSizeChange}
toPage={(page: any) => {
console.log(page);
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query);
}}
/>
)}
</div>
</div>
<Route
path={`${match.url}/add`}
render={() => {
return (
<AddExam
freshList={() => {
let _query: any = { ...queryRef.current };
if (_query.current != 1) {
_query.current = 1;
setQuery(_query)
_query.current = 1;
setQuery(_query);
} else {
getList()
getList();
}
}} />;
}} />
<Route path={`${match.url}/edit/:id`} render={() => {
return <AddExam type='edit' freshList={() => {
getList()
}} />;
}} />
<Route path={`${match.url}/analysic/:id`} render={() => {
return <DataAnalysic />;
}} />
{
modal
}
}}
/>
);
}}
/>
<Route
path={`${match.url}/edit/:id`}
render={() => {
return (
<AddExam
type='edit'
freshList={() => {
getList();
}}
/>
);
}}
/>
<Route
path={`${match.url}/analysic/:id`}
render={() => {
return <DataAnalysic />;
}}
/>
{modal}
</div>
);
}
export default withRouter(ExaminationManager);
\ No newline at end of file
export default withRouter(ExaminationManager);
/*
* @Author: yuananting
* @Date: 2021-04-07 16:10:21
* @LastEditors: wufan
* @LastEditTime: 2021-04-26 10:21:19
* @Description: 助学工具-考试-考试结果
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useEffect } from 'react';
import { Route, withRouter } from 'react-router-dom';
import User from '@/common/js/user';
import './TestDetailPage.less';
import Service from '@/common/js/service';
import { message, Empty } from 'antd';
import moment from 'moment';
import AnswerDescPage from '../components/AnswerDescPage';
import Breadcrumbs from '@/components/Breadcrumbs';
function TestDetailPage(props) {
const examId = props.match.params.testId.replace(/\?.+/, '');
const paperId = window.getParameterByName('paperId');
const [customerId, setCustomerId] = useState('');
const [examDetail, setExamDetail] = useState({
answerAnalysis: '',
resultShow: '',
examDesc: '',
examDuration: 0,
examEndTime: '',
examName: '',
examStartTime: '',
paperId: 0,
passRate: 0,
passScore: 0,
resultContent: '',
totalQuestionCount: 0,
totalScore: 0,
userCorrectQuestion: 0,
userExamDuration: 0,
userExamState: 'INIT',
userScore: 0,
}); // 考试详情
const [testState, setTestState] = useState({
isDelete: false,
}); // 考试状态
const [paperDetail, setPaperDetail] = useState({}); // 试卷详情
const [questionList, setQuestionList] = useState([]); // 试卷题目列表
const [userAnswerList, setUserAnswerList] = useState([]); // 用户答案列表
const [isScrollShow, setIsScrollShow] = useState(false); // 是否展示回到顶部按钮
const {
answerAnalysis,
resultContent,
resultShow,
examName,
examStartTime,
examEndTime,
totalScore,
totalQuestionCount,
passScore,
examDuration,
examDesc,
userExamDuration,
userExamState,
userScore,
userCorrectQuestion,
} = examDetail;
useEffect(() => {
bindScroll();
getPaperDetail();
getExamDetail();
}, []);
function bindScroll() {
window.addEventListener('scroll', handleScroll, true);
}
function getExamDetail() {
const params = {
examId,
source: 0,
tenantId: User.getStoreId() || window.getParameterByName('id'),
userId: User.getCustomerId(),
};
Service.Hades('public/customerHades/queryUserExamResult', params, {
reject: true,
})
.then((res) => {
const data = { ...res.result };
setExamDetail(data);
})
.catch((res) => {
if (res.code === 'EXAM_IS_NOT_EXIST') {
handleChangeShowErrorPage();
} else {
message.error(res.message);
}
});
}
function getPaperDetail() {
Service.Hades('public/customerHades/queryUserExamAnswer', {
examId,
paperId,
source: 0,
readAnswer: true,
tenantId: User.getStoreId() || window.getParameterByName('id'),
userId: User.getCustomerId(),
}).then((res) => {
if (res.success) {
const { paperDetailVO, userExamAnswerVO } = res.result;
const userAnswerList = [...userExamAnswerVO];
const { questionList } = paperDetailVO;
setPaperDetail(paperDetail);
setUserAnswerList(userAnswerList);
setQuestionList(questionList || []);
}
});
}
function handleChangeShowErrorPage() {
setIsShowErrorPage(true);
setTestState({
isDelete: true,
});
}
// 用户时长转换
function formatTime(msTime) {
let time = msTime / 1000;
let hour = Math.floor(time / 60 / 60) % 24 > 9 ? Math.floor(time / 60 / 60) % 24 : '0' + (Math.floor(time / 60 / 60) % 24);
let minute = Math.floor(time / 60) % 60 > 9 ? Math.floor(time / 60) % 60 : '0' + (Math.floor(time / 60) % 60);
let second = time % 60 > 9 ? Math.round(time % 60) : '0' + Math.round(time % 60);
return `${hour}:${minute}:${second}`;
}
// 快速跳转题目
function handleQuickActiveQuestion(questionId) {
let selectDom = document.getElementById(`${questionId}`);
selectDom.scrollIntoView({
block: 'center',
});
}
// 回到顶部
function handleGoTop() {
window.scrollTo(0, 0);
}
// 监听滚动,200以后出现回到顶部按钮
function handleScroll() {
if (window.pageYOffset > 200) {
setIsScrollShow(true);
} else {
setIsScrollShow(false);
}
}
function renderResultInfo() {
console.log('userExamState====>', userExamState);
if (userExamState !== 'INIT') {
if (userExamState === 'FINISH_EXAM') {
if (resultShow === 'IMMEDIATELY' || (resultShow === 'AFTER_EXAM_END' && Date.now() > examEndTime)) {
return (
<div>
<div className='exam-info'>
{['PASS_AND_SCORE', 'ONLY_SCORE'].includes(resultContent) && (
<div className='info-score item'>
<div className='current-score'>
{userScore}
<img src='https://image.xiaomaiketang.com/xm/TsaApiPyxA.png' />
</div>
<div className='origin-data'>总分{totalScore}</div>
</div>
)}
{['PASS_AND_SCORE', 'ONLY_PASS'].includes(resultContent) && (
<div className='info-level item'>
<div className='current-level'>{userScore < passScore ? '不及格' : '及格'}</div>
<div className='origin-data'>及格分{passScore}</div>
</div>
)}
<div className='info-correct item'>
<div className='current-correct'>
{userCorrectQuestion}
<div className='text'>答对题数</div>
</div>
<div className='origin-data'>{totalQuestionCount}</div>
</div>
<div className='info-time item'>
<div className='current-time'>
{formatTime(Number(userExamDuration > examDuration ? examDuration : userExamDuration || 0))}
<div className='text'>用时</div>
</div>
<div className='origin-data'>考试时长{(examDuration || 0) / 60 / 1000}分钟</div>
</div>
</div>
{['ANALYSE_AND_RIGHT_OR_WRONG', 'RIGHT_OR_WRONG'].includes(answerAnalysis) && (
<div className='exam-result'>
<div className='result-title'>
<div className='left-title'>答题情况</div>
<div className='right-tip'>
<span className='correct-num'>{userCorrectQuestion}</span>
<span className='incorrect-num'>/{totalQuestionCount} </span>
道正确
</div>
</div>
<div className='result-content'>
{sortedAnswerList.map((item, index) => {
return (
<div
className='result-content__item'
onClick={() => {
console.log('item', item);
handleQuickActiveQuestion(item.questionId);
}}>
<img
className='icon'
src={
item.isCorrect === 1 ? 'https://image.xiaomaiketang.com/xm/FwZa2Kaypc.png' : 'https://image.xiaomaiketang.com/xm/7tRHDf6ysA.png'
}
/>
<div className='result-content-box'>{index + 1}</div>
</div>
);
})}
</div>
</div>
)}
{!testState.isDelete &&
userExamState !== 'LACK_EXAM' &&
(resultShow === 'IMMEDIATELY' || (resultShow === 'AFTER_EXAM_END' && Date.now() > examEndTime)) &&
answerAnalysis === 'ANALYSE_AND_RIGHT_OR_WRONG' && <AnswerDescPage />}
</div>
);
} else if (resultShow === 'AFTER_EXAM_END') {
return customizeRenderEmpty('after');
}
} else {
return customizeRenderEmpty('lack');
}
}
}
function customizeRenderEmpty(status) {
return (
<div className='empty-result'>
<Empty
image={status === 'lack' ? 'https://image.xiaomaiketang.com/xm/7xi4YTXmHK.png' : 'https://image.xiaomaiketang.com/xm/bhGtST27Hf.png'}
imageStyle={{
height: '100px',
}}
description={
status === 'lack' ? (
<div className='lack-desc'>
<div className='title'>缺考</div>
<div className='content'>你没有成功交卷哦</div>
</div>
) : (
<div className='after-desc'>
<div className='title'>恭喜你完成考试!</div>
<div className='after-show-box'>
<div>本次考试用时 {window.formatHourTime(Number(userExamDuration || 0))}</div>
<div>
考试结果会在
<span style={{ color: '#333333' }}>
{moment(Number(examEndTime)).format('YYYY.MM.DD HH:mm')}
公布哦
</span>
</div>
</div>
</div>
)
}></Empty>
</div>
);
}
let sortedAnswerList = [];
questionList.map((item) => {
userAnswerList &&
userAnswerList.map((answerItem) => {
if (item.questionId === answerItem.questionId) {
sortedAnswerList.push(answerItem);
}
});
});
return (
<div className='exam-result-page page'>
<Breadcrumbs navList={'答题详情'} goBack={props.history.goBack} />
<div className='center'>
<div className='box'>
<div className='box-content'>
<div
className='exam-head'
style={{
padding: examName.length > 20 ? '8px 0 14px' : '16px 0 29px',
}}>
<div className={`exam-name ${examName.length > 20 ? 'many' : 'few'}`}>{examName}</div>
</div>
{renderResultInfo()}
</div>
</div>
</div>
{isScrollShow && <div className='go-top' onClick={handleGoTop}></div>}
</div>
);
}
export default withRouter(TestDetailPage);
.exam-result-page {
margin: 0 auto;
.go-top {
cursor: pointer;
position: fixed;
width: 48px;
height: 48px;
background-image: url('https://image.xiaomaiketang.com/xm/jWix2xDm4t.png');
background-size: 100%;
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.08);
border-radius: 4px;
bottom: 125px;
right: calc(~'(100vw - 1232px)/2');
display: flex;
justify-content: center;
align-items: center;
&:hover {
background-image: url('https://image.xiaomaiketang.com/xm/GHBBNDtTDd.png');
}
}
.box {
.box-content {
position: relative;
width: 840px;
margin: 0 auto;
.exam-head {
color: #333;
text-align: center;
.exam-name {
width: 600px;
font-weight: 500;
margin: 0 auto;
font-size: 24px;
line-height: 33px;
}
.many {
font-size: 21px;
line-height: 29px;
}
.few {
font-size: 25px;
line-height: 36px;
}
}
.empty-result {
box-sizing: border-box;
margin: 0 16px;
background: #ffffff;
box-shadow: 0 -15px 10px 0 rgba(0, 34, 121, 0.1);
border-radius: 4px 4px 0 0;
padding-top: 56px;
.lack-desc {
margin-top: 16px;
.title {
font-size: 17px;
font-weight: 500;
color: #333333;
line-height: 24px;
margin-bottom: 4px;
}
.content {
font-size: 15px;
color: #999999;
line-height: 21px;
}
}
.after-desc {
margin-top: 16px;
.title {
font-size: 17px;
font-weight: 500;
color: #333333;
line-height: 24px;
margin-bottom: 20px;
}
.after-show-box {
// margin: 0 37px;
padding: 11px 21px;
background: #f4f6fa;
border-radius: 4px;
font-size: 15px;
color: #999999;
line-height: 21px;
text-align: center;
}
}
}
.exam-info {
width: 600px;
box-sizing: border-box;
margin: 0 auto;
padding: 24px 22px 12px 22px;
height: 130px;
background: #ffffff;
box-shadow: 0px -10px 10px 0px rgba(0, 61, 214, 0.1);
border-radius: 4px;
display: flex;
flex: 1;
justify-content: center;
.item {
display: flex;
flex-direction: column;
width: 150px;
// height: 130px;
align-items: center;
justify-content: space-between;
position: relative;
.origin-data {
text-align: center;
font-size: 14px;
color: #999999;
line-height: 20px;
}
.line {
&::before {
width: 1px;
height: 40px;
content: '';
position: absolute;
right: 0;
top: 0;
background-color: rgba(232, 232, 232, 1);
}
}
&.info-score {
.current-score {
font-size: 40px;
font-weight: 500;
color: #ff4f4f;
text-align: center;
line-height: 42px;
img {
display: block;
margin: 0 auto;
width: 27px;
height: 12px;
padding-left: 4px;
}
}
}
&.info-level {
.current-level {
font-size: 24px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 33px;
text-align: center;
}
}
&.info-correct {
.current-correct {
display: flex;
flex-direction: column;
padding-bottom: 20px;
justify-content: space-between;
align-items: center;
font-size: 20px;
font-weight: 500;
color: #333333;
line-height: 28px;
.text {
margin-top: 4px;
width: 48px;
height: 17px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9b9b9b;
line-height: 17px;
text-align: center;
}
}
}
&.info-time {
.current-time {
display: flex;
flex-direction: column;
padding-bottom: 20px;
justify-content: space-between;
align-items: center;
font-size: 20px;
font-weight: 500;
color: #333333;
line-height: 28px;
}
.text {
margin-top: 4px;
width: 48px;
height: 17px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9b9b9b;
line-height: 17px;
text-align: center;
}
}
}
}
.left-title {
font-size: 20px;
font-weight: 500;
color: #333333;
position: relative;
margin-left: 8px;
line-height: 28px;
&::before {
position: absolute;
content: '';
left: -8px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: #336dff;
}
}
.exam-result {
padding: 44px 0 24px 0;
vertical-align: middle;
.result-title {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
line-height: 24px;
.right-tip {
font-size: 15px;
font-weight: 500;
color: #999999;
.correct-num {
color: #16e0b7;
}
.incorrect-num {
font-size: 11px;
}
}
}
.result-content {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
&__item {
position: relative;
margin: 0 16px 16px 0;
width: 36px;
height: 36px;
background: #f4f6fa;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:nth-child(5n) {
margin-right: 50px;
}
&:nth-child(15n) {
margin-right: 0px;
}
.icon {
width: 16px;
height: 16px;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, -50%);
}
.result-content-box {
width: 36px;
height: 36px;
font-size: 15px;
line-height: 36px;
background: #f4f6fa;
border-radius: 4px;
text-align: center;
}
}
}
.result-content::after {
content: '';
flex: auto;
}
}
}
}
.footer-btn {
width: 100%;
position: fixed;
bottom: 0;
left: 0;
height: 64px;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid rgba(238, 238, 238, 1);
box-sizing: border-box;
.text {
cursor: pointer;
background: #ffb714;
border-radius: 5px;
width: 342px;
font-size: 15px;
color: #ffffff;
line-height: 44px;
text-align: center;
&.disabled {
background-color: #ccc;
}
}
}
}
import React, { useState, useRef, useEffect } from 'react'
import Service from "@/common/js/service";
import { PageControl } from "@/components";
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 User from "@/common/js/user";
import { ColumnsType } from 'antd/es/table';
import User from '@/common/js/user';
import moment from 'moment';
import './userData.less'
import './userData.less';
const { Search } = Input;
const { Option } = Select;
declare var window: any;
interface sortType {
type: "ascend" | "descend" | null | undefined
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 = [
{
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>,
},
];
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();
})
}
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>
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();
getCustomerId();
}, [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',
width: '30%',
align: 'right',
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: object) {
const { paperId, userExamState } = props.examDetail?.examPaper;
window.RCHistory.push({
pathname: `/test-detail/${props.examId}?paperId=${paperId}&userExamState=${userExamState}`,
});
// console.log(record);
}
//C端 的学员学院列表
function getCustomerId() {
let params = {
userId: User.getUserId(),
};
Service.Hades('public/customerHades/getStoreListCustomer', params).then((res) => {
res.result.map((item: any) => {
if (User.getStoreId() === item.storeId) {
User.setCustomerId(item.storeCustomerId);
}
});
});
}
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="xm-search-filter" style={{ marginTop: 12 }}>
<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}>{(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}>{(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 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>
{
!!allData && <Button style={{ marginBottom: 12 }} onClick={download} >导出</Button>
}
<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: 12 }}>
<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="content">
<Table
bordered
size="small"
columns={columns}
dataSource={list}
onChange={onChange}
pagination={false}
>
</Table>
{total > 0 &&
<PageControl
size="small"
current={query.current - 1}
pageSize={query.size}
total={total}
<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>
toPage={(page: any) => {
console.log(page)
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query)
}}
/>
}
<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'>
<Table bordered size='small' columns={columns} dataSource={list} onChange={onChange} pagination={false}></Table>
{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;
\ No newline at end of file
export default DataAnalysic;
.dataPanal{
border-radius: 4px;
border: 1px solid #E8E8E8;
display: flex;
.item{
text-align: center;
// width: 29.9%;
.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;
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;
}
}
top: 1px;
}
}
&:after {
content: '';
width: 0px;
height: 40px;
position: absolute;
width: 1px;
background-color: rgba(232, 232, 232, 1);
top: 40px;
right: 0px;
}
.exstatus{
width: 4px;
height: 4px;
background: rgb(35, 143, 255);
display: inline-block;
border-radius: 50%;
position: relative;
top: -4px;
&:last-child {
&:after {
display: none;
}
}
}
\ No newline at end of file
}
.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);
}
......@@ -6,61 +6,49 @@
* @Description: 助学工具-题库-题目列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import {
Table,
ConfigProvider,
Empty,
Row,
Input,
Select,
Tooltip,
Space,
Button,
Modal,
message,
} from "antd";
import { PageControl } from "@/components";
import "./QuestionList.less";
import User from "@/common/js/user";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import _ from "underscore";
import PreviewQuestionModal from "../modal/PreviewQuestionModal";
import BatchImportQuestionModal from "../modal/BatchImportQuestionModal";
import { Route, withRouter } from "react-router-dom";
import OperateQuestion from "../OperateQuestion";
import Bus from "@/core/bus";
import React, { Component } from 'react';
import { Table, ConfigProvider, Empty, Row, Input, Select, Tooltip, Space, Button, Modal, message } from 'antd';
import { PageControl } from '@/components';
import './QuestionList.less';
import User from '@/common/js/user';
import AidToolService from '@/domains/aid-tool-domain/AidToolService';
import _ from 'underscore';
import PreviewQuestionModal from '../modal/PreviewQuestionModal';
import BatchImportQuestionModal from '../modal/BatchImportQuestionModal';
import { Route, withRouter } from 'react-router-dom';
import OperateQuestion from '../OperateQuestion';
import Bus from '@/core/bus';
const { Search } = Input;
const questionTypeEnum = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
SINGLE_CHOICE: '单选题',
MULTI_CHOICE: '多选题',
JUDGE: '判断题',
GAP_FILLING: '填空题',
INDEFINITE_CHOICE: '不定项选择题',
};
const questionTypeList = [
{
label: "单选题",
value: "SINGLE_CHOICE",
label: '单选题',
value: 'SINGLE_CHOICE',
},
{
label: "多选题",
value: "MULTI_CHOICE",
label: '多选题',
value: 'MULTI_CHOICE',
},
{
label: "判断题",
value: "JUDGE",
label: '判断题',
value: 'JUDGE',
},
{
label: "填空题",
value: "GAP_FILLING",
label: '填空题',
value: 'GAP_FILLING',
},
{
label: "不定项选择题",
value: "INDEFINITE_CHOICE",
label: '不定项选择题',
value: 'INDEFINITE_CHOICE',
},
];
......@@ -71,7 +59,7 @@ class QuestionList extends Component {
query: {
current: 1,
size: 10,
order: "UPDATED_DESC", // 排序规则[ ACCURACY_DESC, ACCURACY_ASC, CREATED_DESC, CREATED_ASC, UPDATED_DESC, UPDATED_ASC ]
order: 'UPDATED_DESC', // 排序规则[ ACCURACY_DESC, ACCURACY_ASC, CREATED_DESC, CREATED_ASC, UPDATED_DESC, UPDATED_ASC ]
categoryId: null, // 当前题库分类Id
questionName: null, // 题目名称
questionType: null, // 题目类型
......@@ -86,15 +74,14 @@ class QuestionList extends Component {
}
componentDidMount() {
this.queryQuestionPageList();
Bus.bind("queryQuestionPageList", (selectedCategoryId) => {
selectedCategoryId =
selectedCategoryId === "null" ? null : selectedCategoryId;
Bus.bind('queryQuestionPageList', (selectedCategoryId) => {
selectedCategoryId = selectedCategoryId === 'null' ? null : selectedCategoryId;
this.InitSearch(selectedCategoryId);
});
}
componentWillUnmount() {
Bus.unbind("queryQuestionPageList", this.queryQuestionPageList);
Bus.unbind('queryQuestionPageList', this.queryQuestionPageList);
}
// 初始化列表查询
......@@ -103,7 +90,7 @@ class QuestionList extends Component {
...this.state.query,
categoryId,
current: 1,
order: "UPDATED_DESC", // 排序规则
order: 'UPDATED_DESC', // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
......@@ -123,7 +110,7 @@ class QuestionList extends Component {
},
},
() => {
if (searchType === "questionName") return;
if (searchType === 'questionName') return;
this.queryQuestionPageList();
}
);
......@@ -134,7 +121,7 @@ class QuestionList extends Component {
const _query = {
...this.state.query,
current: 1,
order: "UPDATED_DESC", // 排序规则
order: 'UPDATED_DESC', // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
......@@ -156,31 +143,28 @@ class QuestionList extends Component {
const { categoryId } = this.state.query;
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
image='https://image.xiaomaiketang.com/xm/emptyTable.png'
imageStyle={{
height: 100,
}}
description={
<span>
<span>还没有题目</span>
{["CloudManager", "StoreManager"].includes(User.getUserRole()) &&
categoryId && (
<span>
,快去
<span
className="empty-list-tip"
onClick={() => {
this.handleCreateQuestion();
}}
>
新建一个
</span>
吧!
{['CloudManager', 'StoreManager'].includes(User.getUserRole()) && categoryId && (
<span>
,快去
<span
className='empty-list-tip'
onClick={() => {
this.handleCreateQuestion();
}}>
新建一个
</span>
)}
吧!
</span>
)}
</span>
}
></Empty>
}></Empty>
);
};
......@@ -188,59 +172,56 @@ class QuestionList extends Component {
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
let sort = null;
if (columnKey === "accuracy" && order === "ascend") {
sort = "ACCURACY_ASC";
if (columnKey === 'accuracy' && order === 'ascend') {
sort = 'ACCURACY_ASC';
}
if (columnKey === "accuracy" && order === "descend") {
sort = "ACCURACY_DESC";
if (columnKey === 'accuracy' && order === 'descend') {
sort = 'ACCURACY_DESC';
}
if (columnKey === "updateTime" && order === "ascend") {
sort = "UPDATED_ASC";
if (columnKey === 'updateTime' && order === 'ascend') {
sort = 'UPDATED_ASC';
}
if (columnKey === "updateTime" && order === "descend") {
sort = "UPDATED_DESC";
if (columnKey === 'updateTime' && order === 'descend') {
sort = 'UPDATED_DESC';
}
const _query = this.state.query;
_query.order = sort || "UPDATED_DESC";
_query.order = sort || 'UPDATED_DESC';
this.setState({ query: _query }, () => this.queryQuestionPageList());
};
// 表头设置
parseColumns = () => {
// 权限判断
const isPermiss = ["CloudManager", "StoreManager"].includes(
User.getUserRole()
);
const isPermiss = ['CloudManager', 'StoreManager'].includes(User.getUserRole());
const columns = [
{
title: "题目",
key: "questionStem",
dataIndex: "questionStem",
title: '题目',
key: 'questionStem',
dataIndex: 'questionStem',
ellipsis: {
showTitle: false,
},
render: (val) => {
var handleVal = val;
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
handleVal = handleVal.replace(/style\s*?=\s*?([‘"])[\s\S]*?\1/gi, "");
handleVal = handleVal.replace(/<(?!img|input).*?>/g, '');
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, '_、');
handleVal = handleVal.replace(/\&nbsp\;/gi, ' ');
handleVal = handleVal.replace(/style\s*?=\s*?([‘"])[\s\S]*?\1/gi, '');
return (
<Tooltip
overlayClassName="aid-tool-list"
overlayClassName='aid-tool-list'
title={
<div
style={{ maxWidth: 700, width: "auto" }}
style={{ maxWidth: 700, width: 'auto' }}
dangerouslySetInnerHTML={{
__html: handleVal,
}}
/>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
placement='topLeft'
overlayStyle={{ maxWidth: 700 }}>
<div
className="one-line-text"
className='one-line-text'
dangerouslySetInnerHTML={{
__html: handleVal,
}}
......@@ -250,71 +231,56 @@ class QuestionList extends Component {
},
},
{
title: "题型",
key: "questionTypeEnum",
dataIndex: "questionTypeEnum",
width: "16%",
title: '题型',
key: 'questionTypeEnum',
dataIndex: 'questionTypeEnum',
width: '16%',
render: (val) => {
return questionTypeEnum[val];
},
},
{
title: "正确率",
key: "accuracy",
dataIndex: "accuracy",
title: '正确率',
key: 'accuracy',
dataIndex: 'accuracy',
sorter: true,
showSorterTooltip: false,
width: "14%",
width: '14%',
render: (val) => {
return parseInt(val * 100) + "%";
return parseInt(val * 100) + '%';
},
},
{
title: "更新时间",
key: "updateTime",
dataIndex: "updateTime",
title: '更新时间',
key: 'updateTime',
dataIndex: 'updateTime',
sorter: true,
showSorterTooltip: false,
width: "24%",
width: '24%',
render: (val) => {
return formatDate("YYYY-MM-DD H:i:s", val);
return formatDate('YYYY-MM-DD H:i:s', val);
},
},
{
title: "操作",
key: "operate",
dataIndex: "operate",
width: "24%",
title: '操作',
key: 'operate',
dataIndex: 'operate',
width: '24%',
render: (val, record) => {
return (
<div className="record-operate">
<div
className="record-operate__item"
onClick={() => this.previewQuestion(record.id)}
>
<div className='record-operate'>
<div className='record-operate__item' onClick={() => this.previewQuestion(record.id)}>
预览
</div>
{isPermiss && <span className='record-operate__item split'> | </span>}
{isPermiss && (
<span className="record-operate__item split"> | </span>
)}
{isPermiss && (
<div
className="record-operate__item"
onClick={() =>
this.editQuestion(record.id, record.questionTypeEnum)
}
>
<div className='record-operate__item' onClick={() => this.editQuestion(record.id, record.questionTypeEnum)}>
编辑
</div>
)}
{isPermiss && <span className='record-operate__item split'> | </span>}
{isPermiss && (
<span className="record-operate__item split"> | </span>
)}
{isPermiss && (
<div
className="record-operate__item"
onClick={() => this.delQuestionConfirm(record)}
>
<div className='record-operate__item' onClick={() => this.delQuestionConfirm(record)}>
删除
</div>
)}
......@@ -363,13 +329,11 @@ class QuestionList extends Component {
// 删除题目确认弹窗
delQuestionConfirm(record) {
return Modal.confirm({
title: "提示",
content: "确定要删除此题目吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
cancelText: "取消",
title: '提示',
content: '确定要删除此题目吗?',
icon: <span className='icon iconfont default-confirm-icon'>&#xe839; </span>,
okText: '删除',
cancelText: '取消',
onOk: () => {
this.deleteQuestion(record);
},
......@@ -386,7 +350,7 @@ class QuestionList extends Component {
};
AidToolService.deleteQuestion(params).then((res) => {
if (res.success) {
message.success("删除成功");
message.success('删除成功');
const { query, total } = this.state;
const { size, current } = query;
const _query = query;
......@@ -397,7 +361,7 @@ class QuestionList extends Component {
}
this.setState({ query: _query }, () => {
this.queryQuestionPageList();
Bus.trigger("queryCategoryTree", "remain");
Bus.trigger('queryCategoryTree', 'remain');
});
}
});
......@@ -419,7 +383,7 @@ class QuestionList extends Component {
close={() => {
this.setState({ batchImportQuestionModal: null }, () => {
this.queryQuestionPageList();
Bus.trigger("queryCategoryTree", "remain");
Bus.trigger('queryCategoryTree', 'remain');
});
}}
categoryId={categoryId}
......@@ -429,60 +393,50 @@ class QuestionList extends Component {
};
render() {
const {
dataSource = [],
total,
query,
previewQuestionModal,
batchImportQuestionModal,
} = this.state;
const { dataSource = [], total, query, previewQuestionModal, batchImportQuestionModal } = this.state;
const { current, size, categoryId, questionName, questionType } = query;
const { match } = this.props;
return (
<div className="question-list">
<div className="question-list-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-label">题目:</span>
<div className='question-list'>
<div className='question-list-filter'>
<Row type='flex' justify='space-between' align='top'>
<div className='search-condition'>
<div className='search-condition__item'>
<span className='search-label'>题目:</span>
<Search
placeholder="搜索题目名称"
placeholder='搜索题目名称'
value={questionName}
style={{ width: 178 }}
onChange={(e) => {
this.handleChangeQuery("questionName", e.target.value);
this.handleChangeQuery('questionName', e.target.value);
}}
onSearch={() => {
this.queryQuestionPageList();
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="search-label">题型:</span>
<div className='search-condition__item'>
<span className='search-label'>题型:</span>
<Select
placeholder="请选择题目类型"
placeholder='请选择题目类型'
value={questionType}
style={{ width: 178 }}
showSearch
allowClear
enterButton={<span className="icon iconfont">&#xe832;</span>}
filterOption={(inputVal, option) =>
option.props.children.includes(inputVal)
}
filterOption={(inputVal, option) => option.props.children.includes(inputVal)}
onChange={(value) => {
if (_.isEmpty(value)) {
this.handleChangeQuery("questionType", value);
this.handleChangeQuery('questionType', value);
}
}}
onSelect={(value) => {
this.handleChangeQuery("questionType", value);
}}
>
this.handleChangeQuery('questionType', value);
}}>
{_.map(questionTypeList, (item, index) => {
return (
<Select.Option value={item.value} key={item.key}>
<Select.Option value={item.value} key={item.value}>
{item.label}
</Select.Option>
);
......@@ -491,28 +445,24 @@ class QuestionList extends Component {
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={this.handleReset}
>
&#xe61b;{" "}
<div className='reset-fold-area'>
<Tooltip title='清空筛选'>
<span className='resetBtn iconfont icon' onClick={this.handleReset}>
&#xe61b;{' '}
</span>
</Tooltip>
</div>
</Row>
</div>
{["CloudManager", "StoreManager"].includes(User.getUserRole()) &&
categoryId && (
<Space size={16}>
<Button type="primary" onClick={this.handleCreateQuestion}>
新建题目
</Button>
<Button onClick={this.batchImportQuestion}>批量导入</Button>
</Space>
)}
<div className="question-list-content">
{['CloudManager', 'StoreManager'].includes(User.getUserRole()) && categoryId && (
<Space size={16}>
<Button type='primary' onClick={this.handleCreateQuestion}>
新建题目
</Button>
<Button onClick={this.batchImportQuestion}>批量导入</Button>
</Space>
)}
<div className='question-list-content'>
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
<Table
rowKey={(record) => record.id}
......@@ -524,16 +474,14 @@ class QuestionList extends Component {
/>
</ConfigProvider>
{total > 0 && (
<div className="box-footer">
<div className='box-footer'>
<PageControl
current={current - 1}
pageSize={size}
total={total}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.setState({ query: _query }, () =>
this.queryQuestionPageList()
);
this.setState({ query: _query }, () => this.queryQuestionPageList());
}}
showSizeChanger={true}
onShowSizeChange={this.onShowSizeChange}
......@@ -543,10 +491,7 @@ class QuestionList extends Component {
{previewQuestionModal}
{batchImportQuestionModal}
</div>
<Route
path={`${match.url}/question-operate-page`}
component={OperateQuestion}
/>
<Route path={`${match.url}/question-operate-page`} component={OperateQuestion} />
</div>
);
}
......
......@@ -14,13 +14,13 @@ import UserManage from '@/modules/college-manage/UserManagePage';
import StoreDecorationPage from '@/modules/store-manage/StoreDecorationPage';
import CourseCatalogPage from '@/modules/store-manage/CourseCatalogPage';
import LiveCoursePage from '@/modules/course-manage/LiveCoursePage';
import AddLivePage from '@/modules/course-manage/AddLive'
import VideoCoursePage from '@/modules/course-manage/video-course'
import GraphicsCoursePage from '@/modules/course-manage/graphics-course'
import OfflineCoursePage from '@/modules/course-manage/offline-course'
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse'
import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGraphicsCourse'
import AddOfflineCoursePage from '@/modules/course-manage/offline-course/AddOfflineCourse'
import AddLivePage from '@/modules/course-manage/AddLive';
import VideoCoursePage from '@/modules/course-manage/video-course';
import GraphicsCoursePage from '@/modules/course-manage/graphics-course';
import OfflineCoursePage from '@/modules/course-manage/offline-course';
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse';
import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGraphicsCourse';
import AddOfflineCoursePage from '@/modules/course-manage/offline-course/AddOfflineCourse';
// import DataList from '@/modules/course-manage/DataList/DataList';
// import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk';
......@@ -33,145 +33,152 @@ import CourseCategoryManage from '@/modules/teach-tool/components/CourseCategory
import QuestionManageIndex from '@/modules/teach-tool/question-manage/Index';
import PaperManageIndex from '@/modules/teach-tool/paper-manage/Index';
import ExaminationManagerIndex from '@/modules/teach-tool/examination-manager/Index';
import KnowledgeBase from "@/modules/knowledge-base/index";
import ExaminationManagerTestDetail from '@/modules/teach-tool/examination-manager/TestDetailPage';
import KnowledgeBase from '@/modules/knowledge-base/index';
import CollegeInfoPage from '@/modules/college-manage/CollegeInfoPage';
const mainRoutes = [
{
path: "/home",
path: '/home',
component: Home,
name: "中心首页",
name: '中心首页',
},
{
path: "/employees-manage",
path: '/employees-manage',
component: EmployeesManagePage,
name: "员工管理",
name: '员工管理',
},
{
path: '/college-employee',
component: EmployeeManage,
name: '员工管理'
component: EmployeeManage,
name: '员工管理',
},
{
path: '/personal-info',
component: personalInfoPage,
name: '个人信息'
component: personalInfoPage,
name: '个人信息',
},
{
path: "/user-manage",
path: '/user-manage',
component: UserManagePage,
name: "学员管理",
name: '学员管理',
},
{
path: '/college-user',
component: UserManage,
name: '学员管理'
component: UserManage,
name: '学员管理',
},
{
path: '/store-decoration',
component: StoreDecorationPage,
name: '学院装修'
component: StoreDecorationPage,
name: '学院装修',
},
{
path: "/live-course",
path: '/live-course',
component: LiveCoursePage,
name: "直播课",
name: '直播课',
},
{
path: "/video-course",
path: '/video-course',
component: VideoCoursePage,
name: "视频课",
name: '视频课',
},
{
path: "/graphics-course",
path: '/graphics-course',
component: GraphicsCoursePage,
name: "图文课",
name: '图文课',
},
{
path: "/offline-course",
path: '/offline-course',
component: OfflineCoursePage,
name: "线下课",
name: '线下课',
},
{
path: "/create-live-course",
path: '/create-live-course',
component: AddLivePage,
name: "创建直播课",
name: '创建直播课',
},
{
path: "/create-video-course",
path: '/create-video-course',
component: AddVideoCoursePage,
name: "创建视频课",
name: '创建视频课',
},
{
path: "/knowledge-base",
path: '/knowledge-base',
// component:ResourceDisk,
component: KnowledgeBase,
name: "知识库",
name: '知识库',
},
{
path: "/create-graphics-course",
path: '/create-graphics-course',
component: AddGraphicsCoursePage,
name: "创建图文课",
name: '创建图文课',
},
{
path: "/create-offline-course",
path: '/create-offline-course',
component: AddOfflineCoursePage,
name: "创建线下课",
name: '创建线下课',
},
{
path: "/resource-disk",
path: '/resource-disk',
component: ResourceDisk,
name: "资料云盘",
name: '资料云盘',
},
{
path: '/question-manage-index',
component:QuestionManageIndex,
name: '题库'
component: QuestionManageIndex,
name: '题库',
},
{
path: '/paper-manage-index',
component:PaperManageIndex,
name: '试卷'
component: PaperManageIndex,
name: '试卷',
},
{
path: '/examination-manage-index',
component:ExaminationManagerIndex,
name: '考试'
component: ExaminationManagerIndex,
name: '考试',
},
{
path: '/test-detail/:testId',
component: ExaminationManagerTestDetail,
// () => import('@/modules/teach-tool/examination-manager/TestDetailPage'),
name: '答题详情',
},
{
path: '/course-category-manage',
component:CourseCategoryManage,
name: '分类管理'
component: CourseCategoryManage,
name: '分类管理',
},
{
path: "/switch-route",
path: '/switch-route',
component: SwitchRoute,
name: "登录后跳转承载页",
name: '登录后跳转承载页',
},
{
path: "/plan",
path: '/plan',
component: PlanPage,
name: "培训计划",
name: '培训计划',
},
{
path: "/create-plan",
path: '/create-plan',
component: AddPlanPage,
name: "创建视频课",
name: '创建视频课',
},
{
path: '/store-info',
component:StoreInfoPage,
name: '学院信息'
component: StoreInfoPage,
name: '学院信息',
},
{
path: '/college-info',
component: CollegeInfoPage,
name: '学院信息'
component: CollegeInfoPage,
name: '学院信息',
},
{
path: "/learning-data",
path: '/learning-data',
component: LearningDataPage,
name: "学习数据",
name: '学习数据',
},
];
......
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