Commit f6f9fff0 by chenshu

fix:merge

parents 3403f6ba b7d435ff
......@@ -20,7 +20,8 @@ interface FetchParams {
}
interface FetchOptions {
requestType: string // 请求类型 form为表单类型 json为json类型,默认json类型
requestType: string, // 请求类型 form为表单类型 json为json类型,默认json类型
reject: boolean,
}
interface HeadersType{
storeId?:any,
......@@ -33,7 +34,7 @@ class Axios {
method: string,
url: string,
params: any,
options: FetchOptions = { requestType: 'json' }
options: FetchOptions = { requestType: 'json', reject: false }
): Promise<any> {
const _url = `${url}?storeId=${User.getStoreId()}&token=${User.getToken()}&storeUserId=${User.getStoreUserId()}&userId=${User.getUserId()}`;
return new Promise((resolve, reject) => {
......@@ -86,8 +87,9 @@ class Axios {
const { message: ResMessage, success, resultMsg, resultCode } = response.data;
if (success || resultCode === 0) {
return response;
}
} else if (!options.reject) {
message.error(ResMessage || resultMsg);
}
return Promise.reject(response.data);
}, (error): AxiosPromise => {
const requestStatus = error.request.status
......
import React, { useEffect, useState } from 'react';
import { Empty, ConfigProvider, Table } from 'antd';
function XMTable(props) {
const [empty, setEmpty] = useState(props.renderEmpty || {});
const [data, setData] = useState({});
useEffect(() => {
setEmpty(props.renderEmpty || {})
setData(props);
}, [props]);
// 自定义表格空状态
function customizeRenderEmpty() {
return (
<Empty
image={empty.image || Empty.PRESENTED_IMAGE_SIMPLE}
imageStyle={{
height: 150,
}}
description={empty.description}
></Empty>
);
};
return (
<ConfigProvider className="xm-table-component" renderEmpty={customizeRenderEmpty}>
<Table
{...data}
/>
</ConfigProvider>
)
}
export default XMTable;
\ No newline at end of file
.xm-table-component {
}
\ No newline at end of file
/*
* @Author: wufan
* @Date: 2020-11-26 14:48:57
* @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2020-11-26 14:52:02
* @Last Modified by: chenshu
* @Last Modified time: 2021-06-03 15:21:09
*/
import SearchBar from './SearchBar.tsx';
......@@ -10,6 +10,7 @@ import PageControl from './PageControl.tsx';
import CheckBox from './CheckBox.tsx';
import CropperModal from './CropperModal.tsx';
import ImgCutModalNew from './ImgCutModalNew';
import XMTable from './XMTable';
export {
......@@ -17,5 +18,6 @@ export {
PageControl,
CheckBox,
CropperModal,
ImgCutModalNew
ImgCutModalNew,
XMTable,
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: Michael
* @Date: 2017-09-08 17:38:18
* @Last Modified by: chenshu
* @Last Modified time: 2020-08-31 14:55:30
* @Last Modified time: 2021-06-04 15:41:30
*/
@import './variables.less';
......@@ -602,7 +602,7 @@ td.ant-table-column-sort {
background: none;
}
.ant-modal-content .ant-table-thead > tr > th {
padding: 9px 24px;
padding: 9px 24px !important;
}
.ant-modal-content tr > td {
......@@ -766,10 +766,15 @@ td.ant-table-column-sort {
}
}
.ant-table-column-sorters {
justify-content: start !important;
}
.ant-table-column-title {
flex: initial !important;
}
// 排序小三角
.ant-table-column-sorter {
margin-left: 2px !important;
}
.ant-table-column-sorter-full {
margin-top: -0.32rem !important;
margin-left: 8px !important;
}
/*
* @Author: zhujian
* @Date: 2018-10-10 20:49:11
* @Last Modified by: zhujiapeng
* @Last Modified time: 2020-11-16 17:02:11
* @Last Modified by: chenshu
* @Last Modified time: 2021-06-03 16:42:22
*/
// import './s.less'
......@@ -42,6 +42,7 @@ class DefaultIcon extends React.Component {
preserveAspectRatio: 'xMidYMid slice'
}
}
console.log(defaultOptions, this.props, 777777)
return (
<div style={this.props.style} className="DefaultIcon" key="icon">
<Lottie
......
......@@ -12,6 +12,7 @@ import { Input, Button, Tree } from "antd";
import "./Classification.less";
import User from "@/common/js/user";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import Bus from "@/core/bus";
const { Search } = Input;
const { DirectoryTree } = Tree;
......@@ -31,6 +32,11 @@ class Classification extends Component {
componentDidMount() {
this.queryCategoryTree();
Bus.bind('knowledgeCategoryTree', this.queryCategoryTree)
}
componentWillUnmount() {
Bus.unbind('knowledgeCategoryTree', this.queryCategoryTree)
}
shouldComponentUpdate = (nextProps, nextState) => {
......
......@@ -7,17 +7,19 @@
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Table, Modal, message, Tooltip, Switch, Dropdown, Button } from 'antd';
import { Route, withRouter } from 'react-router-dom';
import { PageControl } from '@/components';
import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import ScanFileModal from '../../resource-disk/modal/ScanFileModal';
import WatchData from './WatchData';
import KnowledgeAPI from '@/data-source/knowledge/request-api';
import ENUM from '../ENUM.js';
import './KnowledgeBaseList.less';
import React from "react";
import { Modal, message, Tooltip, Switch, Dropdown, Button } from "antd";
import { Route, withRouter } from "react-router-dom";
import Lottie from 'react-lottie';
import { PageControl, XMTable } from "@/components";
import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass";
import { appId, shareUrl, LIVE_SHARE } from "@/domains/course-domain/constants";
import ScanFileModal from "../../resource-disk/modal/ScanFileModal";
import WatchData from "./WatchData";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import ENUM from "../ENUM.js";
import * as nodata from '../../lottie/nodata/data.json';
import "./KnowledgeBaseList.less";
const DEFAULT_SIZE_UNIT = 1000 * 1000; // 将B转换成M
const { confirm } = Modal;
......@@ -127,7 +129,6 @@ class KnowledgeBaseList extends React.Component {
// 预览文件
handleScanFile = (folder) => {
console.log(folder);
const { folderFormat, folderSize, ossUrl } = folder;
switch (folderFormat) {
case 'PDF':
......@@ -392,8 +393,7 @@ class KnowledgeBaseList extends React.Component {
width: 160,
fixed: 'right',
render: (val, record, index) => {
console.log(this.props.categoryId);
return this.props.categoryId === '0' ? (
return this.props.categoryId === "0" ? (
<div className='operate'>
<div className='operate__item' onClick={() => this.handleDelete(record)}>
移出
......@@ -441,8 +441,7 @@ class KnowledgeBaseList extends React.Component {
// 前往上课数据页面
handleLinkToClassData = (item) => {
const { match } = this.props;
console.log(item);
localStorage.setItem('WatchData_CourseName', item.name);
localStorage.setItem("WatchData_CourseName", item.name);
window.RCHistory.push({
// pathname: `${match.url}/course-data?type=${item.courseType}&id=${item.liveCourseId}`,
pathname: `${match.url}/course-data?type=${item.type}&id=${item.id}`,
......@@ -456,9 +455,13 @@ class KnowledgeBaseList extends React.Component {
};
KnowledgeAPI.delKnowledge(params).then((res) => {
if (res.success) {
message.success('移出成功');
this.props.onChange();
this.props.updateCategoryTree();
const { onChange, updateCategoryTree, selectedRowKeys, onSelectChange } = this.props;
message.success("移出成功");
onChange();
updateCategoryTree();
if (selectedRowKeys.includes(item.id)) {
onSelectChange(_.reject(selectedRowKeys, value => value === item.id));
}
}
});
};
......@@ -473,19 +476,45 @@ class KnowledgeBaseList extends React.Component {
};
render() {
const { dataSource = [], totalCount, query, match } = this.props;
const { dataSource = [], totalCount, query, match, selectedRowKeys, onSelectChange } = this.props;
const { current, size } = query;
const rowSelection = {
selectedRowKeys,
preserveSelectedRowKeys: true,
onChange: onSelectChange,
}
const defaultOptions = {
loop: true,
autoplay: true,
animationData: nodata,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice'
}
}
return (
<div className='knowledge-base-list'>
<Table
<div className="knowledge-base-list">
<XMTable
rowKey={(record) => record.id}
size='middle'
rowSelection={rowSelection}
size="middle"
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
scroll={{ x: 900 }}
bordered
className='knowledge-list-table'
className="knowledge-list-table"
renderEmpty={{
image: <div style={{ marginTop: 24 }}>
<Lottie
options={defaultOptions}
height={150}
width={150}
isStopped={false}
isPaused={false}
/>
</div>,
description: <span style={{ display: 'block', paddingBottom: 24 }}>暂无数据</span>
}}
/>
<div className='box-footer'>
......
......@@ -9,19 +9,27 @@
import React, { useState, useEffect } from "react";
import { Button, Menu, Dropdown,message } from "antd";
import { Button, Menu, Dropdown,message, Modal } from "antd";
import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal";
import { DownOutlined } from "@ant-design/icons";
import AddCourse from "../modal/AddCourse";
import User from "@/common/js/user";
import Service from "@/common/js/service";
import KnowledgeAPI from "@/data-source/knowledge/request-api";
import MoveModal from '../../teach-tool/modal/MoveModal';
import Bus from "@/core/bus";
import './KnowledgeBaseOpt.less';
export default function KnowledgeBaseOpt({
categoryId,
selectedRowKeys,
updateCategoryTree,
onChange,
onChangeKeys,
}) {
const [modal, setModal] = useState(null);
const [openMoveModal, setOpenMoveModal] = useState(false);
const [data, setData] = useState([]);
const menu = (
<Menu>
......@@ -34,6 +42,40 @@ export default function KnowledgeBaseOpt({
</Menu>
);
useEffect(() => {
queryCategoryTree();
}, []);
useEffect(() => {
clearSelect();
}, [categoryId])
// 查询分类树
function queryCategoryTree() {
let query = {
storeId: User.getStoreId(),
withCount: true,
};
KnowledgeAPI.getCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
let list = renderTreeNodes(categoryList);
setData(list);
});
}
function renderTreeNodes(data) {
let newTreeData = data.map((item) => {
item.title = item.categoryName;
item.value = item.id;
item.key = item.id;
if (item.sonCategoryList) {
item.children = renderTreeNodes(item.sonCategoryList);
}
return item;
});
return newTreeData;
};
function handAddCourse() {
let modal = (
<AddCourse
......@@ -86,16 +128,127 @@ export default function KnowledgeBaseOpt({
setModal(modal);
}
function batchMove() {
if (_.isEmpty(selectedRowKeys)) {
message.warning('请先选择要移动的知识');
return null;
}
setOpenMoveModal(true);
}
function batchMoveRemote(categoryId) {
const data = {
categoryId,
ids: selectedRowKeys,
storeId: User.getStoreId(),
};
Service.Hades('public/hades/batchMoveKnowledgeCategory', data, { reject: true }).then((res) => {
if (res.success) {
message.success('移动成功');
onChange();
Bus.trigger('knowledgeCategoryTree');
clearSelect();
} else {
message.error('移动失败');
}
}).catch(() => {
message.error('移动失败');
})
}
function batchDelete() {
if (_.isEmpty(selectedRowKeys)) {
message.warning('请先选择要删除的知识');
return null;
}
Modal.confirm({
title: "确定要删除知识吗?",
content: "删除后,不可恢复。",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
cancelText: "取消",
onOk: () => {
const data = {
ids: selectedRowKeys,
storeId: User.getStoreId(),
};
Service.Hades('public/hades/batchDelKnowledge', data, { reject: true }).then((res) => {
if (res.success) {
message.success('删除成功');
onChange();
Bus.trigger('knowledgeCategoryTree');
clearSelect();
} else {
message.error('删除失败');
}
}).catch(() => {
message.error('删除失败');
})
},
})
}
function onSelectPaper(keys) {
if (keys.length > 50) {
message.warning('最多只能选择50个知识');
return null;
}
onChangeKeys(keys);
};
function clearSelect() {
onChangeKeys([]);
}
return (
<div className="video-course-opt">
<Dropdown overlay={menu}>
<Button type="primary" className="mr12">
<div className="knowledge-course-opt">
{_.isEmpty(selectedRowKeys) ?
(categoryId !== '0' && <Dropdown overlay={menu}>
<Button type="primary" className="mr8">
添加知识
<DownOutlined />
</Button>
</Dropdown>
</Dropdown>)
: <div className="select-container">
<span className="con">
<div>
<span className="icon iconfont tip">&#xe6f2;</span>
<span className="text">已选择{selectedRowKeys.length}</span>
</div>
<div>
<span className="clear" onClick={clearSelect}>清空</span>
</div>
</span>
</div>
}
<Button
className="mr8"
onClick={() => {
batchMove();
}}
>批量移动</Button>
<Button
onClick={() => {
batchDelete();
}}
>批量删除</Button>
{modal}
{openMoveModal &&
<MoveModal
visible={openMoveModal}
title="知识"
data={data}
categoryId={categoryId}
length={selectedRowKeys.length}
onCancel={() => setOpenMoveModal(false)}
onOk={(categoryId) => {
batchMoveRemote(categoryId);
setOpenMoveModal(false);
}}
/>
}
</div>
);
}
.knowledge-course-opt {
display: flex;
.select-container{
margin-right: 24px;
.con{
background: #E9EFFF;
border-radius: 4px;
padding: 3px 16px;
display: inline-flex;
align-items: center;
justify-content: space-between;
.tip{
font-size:14px;
color:#2966FF;
margin-right:8px;
}
.text{
font-size:14px;
color:#666;
margin-right:30px;
}
.clear{
color:#5289FA;
font-size:14px;
}
}
}
}
\ No newline at end of file
import React from 'react';
import KnowledgeBaseFilter from './components/KnowledgeBaseFilter';
import KnowledgeBaseOpt from './components/KnowledgeBaseOpt';
import KnowledgeBaseList from './components/KnowledgeBaseList';
import Classification from './components/Classification';
/*
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 10:43:10
* @LastEditors: wufan
* @LastEditTime: 2021-05-30 20:36:42
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from "react";
import { message } from 'antd'
import KnowledgeBaseFilter from "./components/KnowledgeBaseFilter";
import KnowledgeBaseOpt from "./components/KnowledgeBaseOpt";
import KnowledgeBaseList from "./components/KnowledgeBaseList";
import Classification from "./components/Classification";
import KnowledgeAPI from '@/data-source/knowledge/request-api';
import User from '@/common/js/user';
......@@ -22,6 +30,7 @@ export default class KnowledgeBase extends React.Component {
totalCount: 0, // 知识库数据总条数
categoryId: '0',
updateCategoryFlag: false,
selectedRowKeys: [],
};
}
......@@ -65,8 +74,23 @@ export default class KnowledgeBase extends React.Component {
});
};
onSelectChange = (selectedRowKeys) => {
if (selectedRowKeys.length > 50) {
message.warning('最多只能选择50个题目');
}
const list = _.filter(selectedRowKeys, (item, index) => index < 50);
this.setState({ selectedRowKeys: list });
};
render() {
const { dataSource, totalCount, query, categoryId, updateCategoryFlag } = this.state;
const {
dataSource,
totalCount,
query,
categoryId,
updateCategoryFlag,
selectedRowKeys,
} = this.state;
return (
<div className='page'>
<div className='content-header'>知识库</div>
......@@ -81,13 +105,19 @@ export default class KnowledgeBase extends React.Component {
<KnowledgeBaseFilter onChange={this.handleFetchScheduleList} />
{/* 操作模块 */}
{categoryId !== '0' && (
<KnowledgeBaseOpt onChange={this.handleFetchScheduleList} updateCategoryTree={this.updateCategoryTree} categoryId={categoryId} />
)}
<KnowledgeBaseOpt
onChange={this.handleFetchScheduleList}
updateCategoryTree={this.updateCategoryTree}
categoryId={categoryId}
selectedRowKeys={selectedRowKeys}
onChangeKeys={(keys) => this.setState({ selectedRowKeys: keys })}
/>
{/* 知识库列表模块 */}
<KnowledgeBaseList
query={query}
selectedRowKeys={selectedRowKeys}
onSelectChange={this.onSelectChange}
categoryId={categoryId}
dataSource={dataSource}
totalCount={totalCount}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* @Author: yuananting
* @Date: 2021-02-23 18:28:50
* @LastEditors: fusanqiasng
* @LastEditTime: 2021-05-21 17:57:59
* @LastEditors: yuananting
* @LastEditTime: 2021-06-02 14:25:06
* @Description: 助学工具-课程分类
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -153,9 +153,7 @@ class CourseCategoryManage extends Component {
<Space className='title-opts' size={16}>
<span
onClick={() => {
let nodesCount = 0
const { originTreeData } = this.state
console.log('orororo', originTreeData)
const { originTreeData } = this.state;
if (
(item.categoryLevel === 0 && originTreeData.length >= 29) ||
(item.categoryLevel > 0 && this.getRelatedNodes(item.parentId).length >= 30)
......@@ -343,8 +341,10 @@ class CourseCategoryManage extends Component {
let dragNodes = []
dragNodes.push(dragNode.id)
if (dragNode.parentId != 0) {
dragNodes = dragNodes.concat(this.getParentDragNodesLevel(this.state.treeMap[dragNode.parentId]))
if (dragNode.parentId !== "0") {
dragNodes = dragNodes.concat(
this.getParentDragNodesLevel(this.state.treeMap[dragNode.parentId])
);
}
return dragNodes
}
......@@ -365,14 +365,24 @@ class CourseCategoryManage extends Component {
// 拖拽
onDrop = (info) => {
if (this.state.categoryName) {
return
// 带搜索时的分类树不允许拖拽
// 不允许其他节点拖拽到未分类中
// 不允许其他节点拖拽到未分类之前
if (
this.state.categoryName ||
(info.node.categoryName === "未分类" && info.dropPosition === 0) ||
(info.node.categoryName === "未分类" &&
info.dropToGap &&
info.dropPosition === -1)
) {
return;
}
// 未分类不可以拖拽
if (info.dragNode.categoryName === '未分类' && info.dragNode.categoryLevel === 0) return message.info('未分类”为默认分类暂不支持移动')
// 不允许其他节点拖拽到未分类之前
if (info.node.categoryName === '未分类' && info.dropToGap && info.dropPosition === -1) return
if (
info.dragNode.categoryName === "未分类" &&
info.dragNode.categoryLevel === 0
)
return message.info("“未分类”为默认分类暂不支持移动");
let targetParentId = info.dropToGap ? info.node.parentId : info.node.id
let relatedNodes = this.getRelatedNodes(targetParentId)
......@@ -383,8 +393,7 @@ class CourseCategoryManage extends Component {
let nodesArr = this.getDragNodesLevel(this.state.treeMap[info.dragNode.id])
let parentArr = this.getParentDragNodesLevel(this.state.treeMap[targetParentId])
if (nodesArr.length + parentArr.length > 4) {
console.log(nodesArr.length, parentArr.length)
return message.info('最多支持5级分类')
return message.info("最多支持5级分类");
}
}
if (relatedNodes && relatedNodes.length >= 30) {
......
......@@ -44,7 +44,7 @@
white-space: nowrap;
}
.ant-tree-node-content-wrapper.ant-tree-node-selected {
color: #666666;
color: #2966FF;
}
}
.ant-tree-treenode-selected:hover::before,
......
.examPage{
padding-bottom: 50px;
.box {
padding-bottom: 40px!important;
padding-bottom: 66px!important;
}
.ant-form-item{
margin-bottom: 24px !important;
&:last-child{
margin-bottom: 0px !important;
}
}
.form{
margin-top: 12px;
margin-top: 24px;
margin-bottom: 32px;
width: 1000px;
.title{
position: relative;
padding-left: 28px;
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: bold;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
margin-bottom: 24px;
&.first {
&::before{
width:4px;
height:12px;
content:'';
background-image: linear-gradient(#2966FF 83.5%, #0ACCA4 16.5%);
display:inline-block;
position: absolute;
left:16px;
top:6px;
}
}
}
}
......
import React, { useState, useRef, useEffect, useContext } from 'react'
import Breadcrumbs from "@/components/Breadcrumbs";
import { Form, Alert, Input, Button, InputNumber, DatePicker, Switch, Radio, message, Modal } from 'antd';
import { Form, Alert, Input, Button, InputNumber, DatePicker, Switch, Radio, message, Modal, Tooltip } from 'antd';
import { Route, withRouter } from 'react-router-dom';
import User from "@/common/js/user";
import moment from 'moment'
......@@ -36,20 +36,27 @@ function AddExam(props: any) {
const [getData, setGetData] = useState(false);
const [preview, setPreview] = useState(false);
const [examTotal, setExamTotal] = useState(0);
const timer = useRef({});
const request = useRef(false);
const { match } = props;
const [examDuration, setExamDuration] = useState(undefined);
useEffect(() => {
if (props.type === 'edit') {
queryExamDetail()
switch (props.type) {
case "copy": // 考试列表-复制考试进入
case "edit": // 考试列表-编辑考试进入
queryExamDetail();
break;
case "organizeExam": // 试卷列表-组织考试进入
case "newPaperToAddExam": // 组卷页面-新建保存试卷并组织考试
case "editPaperToAddExam": // 组卷页面-编辑保存试卷并组织考试
setGetData(true);
setPaperInfo(props.paperInfo);
break;
}
}, [])
useEffect(() => {
console.log(paperInfo)
setPaperId(paperInfo.paperId)
setPassRate(paperInfo.passRate)
......@@ -75,9 +82,9 @@ function AddExam(props: any) {
const { result } = res
setPaperInfo(result.examPaper)
setPaperId(result.examPaper.paperId)
setStartTime(result.examStartTime)
setExamEndTime(result.examEndTime)
setExamName(result.examName)
setStartTime(props.type === 'edit' ? result.examStartTime : '')
setExamEndTime(props.type === 'edit' ? result.examEndTime : '')
setExamName(props.type === 'edit' ? result.examName : `${result.examName}(复制)`)
setPassRate(result.passRate * 100)
setNeedPhone(result.needPhone)
setExamDesc(result.examDesc)
......@@ -125,6 +132,11 @@ function AddExam(props: any) {
return
}
if (param.examName && param.examName.length > 40) {
message.warning('考试名称最多40字');
return
}
if (!paperId) {
message.warning('请选择试卷');
return
......@@ -174,8 +186,19 @@ function AddExam(props: any) {
Service.Hades(props.type === 'edit' ? 'public/hades/editExam' : "public/hades/createExam", param).then((res) => {
message.success(props.type === 'edit' ? '编辑成功' : '创建成功');
switch (props.type) {
case "organizeExam": // 试卷列表-组织考试进入
case "newPaperToAddExam": // 组卷保存组织考试
case "editPaperToAddExam":
window.RCHistory.push("/examination-manage-index")
break;
case "add":
case "edit": // 考试列表-新建或编辑
case "copy": // 考试列表-新建或编辑
props.freshList()
props.history.goBack();
break;
}
})
}
......@@ -222,14 +245,30 @@ function AddExam(props: any) {
cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
props.history.goBack();
window.RCHistory.push("/examination-manage-index")
}
})
}
let title = '';
switch (props.type) {
case 'add':
case "organizeExam":
case "newPaperToAddExam":
case "editPaperToAddExam":
title = '新建考试';
break;
case 'edit':
title = '编辑考试';
break;
case 'copy':
title = '复制考试';
break;
default:
break;
}
return <div className="page examPage">
<Breadcrumbs navList={props.type === 'edit' ? "编辑考试" : "新建考试"} goBack={handleGoBack} />
<Breadcrumbs navList={title} goBack={handleGoBack} />
<div className="box">
<div className="show-tips">
......@@ -242,8 +281,8 @@ function AddExam(props: any) {
layout="horizontal"
>
<Form.Item label="考试名称"
validateStatus={(check && !examName) ? 'error' : ''}
help={check && !examName && '请选择课程'}
validateStatus={(check && (!examName ? '请输入考试名称' : (examName.length > 40) && '考试名称最多40字')) ? 'error' : ''}
help={check && (!examName ? '请输入考试名称' : (examName.length > 40) && '考试名称最多40字')}
required>
<Input placeholder='请输入考试名称(40字以内)' maxLength={40} value={examName} onChange={(e) => {
......@@ -296,11 +335,18 @@ function AddExam(props: any) {
<Form.Item label="及格线"
<Form.Item
label={<div>
<span>及格线</span>
<Tooltip title="默认为选中试卷所设置的及格线,可修改">
<span className="icon iconfont" style={{ color: '#BFBFBF', marginLeft: 4 }}>&#xe61d;</span>
</Tooltip>
</div>}
style={{ marginTop: 24 }}
validateStatus={(check && !passRate) ? 'error' : ''}
help={check && !passRate && '请输入及格线'}
required>
required
>
<InputNumber value={passRate} min={0} max={100} onChange={(value: any) => { setPassRate(parseInt(value)) }} style={{ width: 100 }} />
<span style={{ marginLeft: 4 }}>%
</span>
......@@ -314,10 +360,11 @@ function AddExam(props: any) {
required>
<RangePicker
style={{ width: 320 }}
showTime={{ defaultValue: [moment().add(5, 'minutes'), moment().add(5, 'minutes')] }}
ranges={{
'近七天': [moment(), moment().add(6, 'day').endOf('day')],
'近1个月': [moment(), moment().add(1, 'month').endOf('day')],
'近3个月': [moment(), moment().add(3, 'month').endOf('day')],
'近七天': [moment().add(5, 'minute'), moment().add(6, 'day').endOf('day')],
'近1个月': [moment().add(5, 'minute'), moment().add(1, 'month').endOf('day')],
'近3个月': [moment().add(5, 'minute'), moment().add(3, 'month').endOf('day')],
}}
disabledDate={disabledDate}
value={[
......@@ -325,7 +372,6 @@ function AddExam(props: any) {
examEndTime ? moment(Number(examEndTime)) : null
]}
disabledTime={disabledRangeTime}
showTime
format="YYYY/MM/DD HH:mm"
onChange={(date: any) => {
setStartTime(date && date[0]?.valueOf());
......@@ -350,7 +396,7 @@ function AddExam(props: any) {
help={check && (desclen > 1000) && '最多只能输入1000个字'}
>
{
(getData || (props.type !== 'edit')) && <GraphicsEditor
(getData || (props.type === 'add')) && <GraphicsEditor
maxLimit={1000}
isIntro={true}
detail={{
......
......@@ -39,11 +39,10 @@ function DataAnalysic(props: any) {
<Breadcrumbs navList={"考试数据"} goBack={props.history.goBack} />
<div className="box">
<div className="titleBox ">
<span className='tips'></span>
考试名称:{examDetail.examName}
</div>
</div>
<div className="box">
<div className="box" style={{ paddingTop: 0 }}>
<Tabs activeKey={selectKey} onChange={(key: any) => {
setSelectKey(key)
}}>
......
......@@ -207,7 +207,6 @@ function ExamData(props: any) {
return <div className="rr">
<a
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
......
import React, { useState, useRef, useEffect, useContext } from 'react'
import { Input, Select, DatePicker, Tooltip, Button, Table, Dropdown, Menu, Modal } from 'antd';
import Lottie from 'react-lottie';
import TeacherSelect from '@/modules/common/TeacherSelect';
import { Route, withRouter } from 'react-router-dom';
import Service from "@/common/js/service";
import moment from 'moment';
import { PageControl } from "@/components";
import { PageControl, XMTable } 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 PreviewModal from './PreviewModal'
import * as nodata from '../../lottie/nodata/data.json';
import './index.less'
const { RangePicker } = DatePicker;
const { Search } = Input;
......@@ -53,6 +56,8 @@ function ExaminationManager(props: any) {
const [order, setOrder] = useState(sortStatus.type);
const [modal, setModal] = useState(null);
const [questionCntSort, setQuestionCntSort] = useState(sortState)
const [openPreviewModal, setOpenPreviewModal] = useState(false);
const [info, setInfo] = useState({ examDuration: 0 });
const queryRef = useRef({});
const orderEnum = {
......@@ -65,8 +70,8 @@ function ExaminationManager(props: any) {
descend: 'PASS_CNT_DESC'
},
examCreateTime: {
ascend: 'EXAM_START_TIME_ASC',
descend: 'EXAM_START_TIME_DESC'
ascend: 'CREATED_ASC',
descend: 'CREATED_DESC'
}
}
......@@ -163,12 +168,10 @@ function ExaminationManager(props: any) {
>
分享
</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="operate__item split" > | </span>
<Dropdown overlay={getOpe(record)}>
<span className='more'>更多</span>
</Dropdown>]
}
</Dropdown>
......@@ -178,6 +181,18 @@ function ExaminationManager(props: any) {
];
function queryExamDetail(examId: string) {
Service.Hades("public/hades/queryExamDetail", {
examId,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0
}).then((res) => {
setInfo(res.result);
setOpenPreviewModal(true);
});
}
function shareModal(record: any) {
const modal = <ExamShareModal
data={record}
......@@ -190,8 +205,14 @@ function ExaminationManager(props: any) {
function getOpe(item: any) {
return <Menu>
<Menu.Item
key="1"
onClick={() => {
queryExamDetail(item.examId);
}}
>预览</Menu.Item>
{
ctx.xmState?.userPermission?.AddExam() && (moment().valueOf() < item.examStartTime) && <Menu.Item key="0">
ctx.xmState?.userPermission?.AddExam() && (moment().valueOf() < item.examStartTime) && <Menu.Item key="2">
<span
onClick={() => {
if (moment().valueOf() + 5 * 60 * 1000 > item.examStartTime) {
......@@ -213,8 +234,11 @@ function ExaminationManager(props: any) {
</Menu.Item>
}
{ctx.xmState?.userPermission?.AddExam() &&
<Menu.Item key="3" onClick={() => props.history.push(`${match.url}/copy/${item.examId}`)}>复制</Menu.Item>
}
{
ctx.xmState?.userPermission?.DelExam() && (moment().valueOf() + 30 * 60 * 1000 < item.examStartTime) && <Menu.Item key="1">
ctx.xmState?.userPermission?.DelExam() && ((moment().valueOf() + 30 * 60 * 1000 < item.examStartTime) || (moment().valueOf() > item.examEndTime)) && <Menu.Item key="4">
<span
onClick={() => {
deleteExam(item)
......@@ -281,12 +305,19 @@ function ExaminationManager(props: any) {
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)
}
const defaultOptions = {
loop: true,
autoplay: true,
animationData: nodata,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice'
}
}
return <div className="page examination-manager">
<div className="content-header">考试</div>
<div className="box content-body">
......@@ -381,7 +412,7 @@ function ExaminationManager(props: any) {
<div className="content">
<Table
<XMTable
bordered
size="small"
columns={columns}
......@@ -390,8 +421,20 @@ function ExaminationManager(props: any) {
onChange={onChange}
pagination={false}
style={{ margin: '0px 0 16px' }}
renderEmpty={{
image: <div style={{ marginTop: 24 }}>
<Lottie
options={defaultOptions}
height={150}
width={150}
isStopped={false}
isPaused={false}
/>
</div>,
description: <span style={{ display: 'block', paddingBottom: 24 }}>暂无数据</span>
}}
>
</Table>
</XMTable>
{total > 0 &&
<PageControl
size="small"
......@@ -400,7 +443,6 @@ function ExaminationManager(props: any) {
total={total}
onShowSizeChange={onShowSizeChange}
toPage={(page: any) => {
console.log(page)
let _query: any = { ...queryRef.current };
_query.current = page + 1;
setQuery(_query)
......@@ -409,8 +451,14 @@ function ExaminationManager(props: any) {
}
</div>
</div>
{openPreviewModal &&
<PreviewModal
info={{ ...info, examDuration: (info.examDuration || 0) / 60000 }}
onClose={() => { setOpenPreviewModal(false) }}
/>
}
<Route path={`${match.url}/add`} render={() => {
return <AddExam freshList={() => {
return <AddExam type="add" freshList={() => {
let _query: any = { ...queryRef.current };
if (_query.current != 1) {
_query.current = 1;
......@@ -418,8 +466,6 @@ function ExaminationManager(props: any) {
} else {
getList()
}
}} />;
}} />
<Route path={`${match.url}/edit/:id`} render={() => {
......@@ -427,6 +473,11 @@ function ExaminationManager(props: any) {
getList()
}} />;
}} />
<Route path={`${match.url}/copy/:id`} render={() => {
return <AddExam type='copy' freshList={() => {
getList()
}} />;
}} />
<Route path={`${match.url}/analysic/:id`} render={() => {
return <DataAnalysic />;
......
......@@ -123,6 +123,7 @@
border-radius: 4px;
padding: 14px;
padding-bottom: 8px;
padding-right: 0;
.item{
margin-bottom: 8px;
.name{
......
......@@ -33,18 +33,18 @@ function PreviewModal(props: any) {
<div className="phone">
<div className="content">
<div className="topContent">
<div className="title" style={{ fontSize: props.info.examName.length > 24 ? 13 : 22 ,marginTop:props.info.examName.length > 24?44:20}}>{props.info.examName || ' '}</div>
<div className="title" style={{ fontSize: props.info.examName.length > 24 ? 13 : 22 ,marginTop: 20 }}>{(props.info.examName.length > 40 ? props.info.examName.substring(0, 40) : props.info.examName) || ' '}</div>
{
props.info.examStartTime && <div className="time">{moment(props.info.examStartTime).format("YYYY-MM-DD HH:mm")}~{moment(props.info.examEndTime).format("YYYY-MM-DD HH:mm")}</div>
}
<div className="rule">
<div className="item">
<div className="num">{props.info.totalScore || 0}</div>
<div className="num">{props.info.totalScore || (props.info.examPaper || {}).totalScore || 0}</div>
<div className="text">总分 <span className="dw" style={{color:'#999'}}>(分)</span></div>
</div>
<div className="item">
<div className="num">{props.info.examTotal || 0}</div>
<div className="num">{props.info.examTotal || (props.info.examPaper || {}).questionCnt || 0}</div>
<div className="text">总题数<span className="dw" style={{color:'#999'}} >(道)</span></div>
</div>
<div className="item">
......
......@@ -182,7 +182,6 @@ function DataAnalysic(props: any) {
return <div className="rr">
<a
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
......@@ -217,7 +216,7 @@ function DataAnalysic(props: any) {
</div>
</div>
<div className="xm-search-filter" style={{ marginTop: 12 }}>
<div className="xm-search-filter" style={{ marginTop: 16 }}>
<div style={{ display: 'flex' }}>
<div className="search-condition">
<div className="search-condition__item">
......
.dataAnalysic{
.titleBox{
position: relative;
padding-left: 28px;
font-size: 19px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
background: #FFFFFF;
// padding: 20px 24px;
// margin-bottom: 8px;
.tips{
width: 4px;
height: 16px;
background: #336DFF;
display: inline-block;
margin-right: 4px;
&::before{
width:4px;
height:12px;
content:'';
background-image: linear-gradient(#2966FF 83.5%, #0ACCA4 16.5%);
display:inline-block;
position: absolute;
left:16px;
top:7px;
}
}
.ant-tabs-content-holder {
margin-top: 8px;
}
}
\ No newline at end of file
import React from 'react';
import { Modal, TreeSelect } from 'antd';
import './MoveModal.less';
class MoveModal extends React.Component {
constructor(props) {
super(props);
this.state = {
categoryId: undefined,
};
}
handleChangeCatalogList = (value, label) => {
this.setState({ categoryId: value, categoryName: label[0] });
};
filterData = (data, id) => {
let newTreeData = data.map((item) => {
item.disabled = id === item.id;
if (item.children) {
item.children = this.filterData(item.children, id);
}
return item;
});
return newTreeData;
}
render() {
const { visible, title, onCancel, onOk, data, length, categoryId: id } = this.props;
const { categoryId } = this.state;
const moveData = this.filterData(data, id);
return (
<Modal
title={`移动${title}`}
visible={visible}
onCancel={onCancel}
maskClosable={false}
className="common-move-modal"
onOk={() => onOk(categoryId)}
>
<div className="tip">
<span className="icon iconfont">&#xe6f2;</span>
<span className="text">已选择<span style={{ color: '#2966FF' }}>{length}</span>{title},移动后,原有分类将移除此{title}</span>
</div>
<div className="move-item" id="move-item">
<span className="label">选择分类:</span>
<TreeSelect
showSearch
treeNodeFilterProp="title"
getPopupContainer={() => document.querySelector('#move-item')}
style={{ width: 240 }}
treeData={moveData}
placeholder="请选择分类"
allowClear
value={categoryId}
onChange={(value, label) => {
this.handleChangeCatalogList(value, label);
}}
/>
</div>
</Modal>
)
}
}
export default MoveModal;
\ No newline at end of file
.common-move-modal {
.tip {
display: flex;
align-items: center;
margin-bottom: 16px;
.iconfont {
font-size: 14px;
color: #BFBFBF;
margin-right: 8px;
}
.text {
color: #999;
font-size: 14px;
}
}
.move-item {
.label {
color: #333;
}
}
.ant-select-tree .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected {
background: rgba(41, 102, 255, 0.06);
}
}
\ No newline at end of file
.operate-paper-page {
.box {
margin-bottom: 52px !important;
.ant-form-item {
margin-bottom: 24px !important;
}
.table-style {
border: 1px solid #f0f0f0 !important;
}
......@@ -12,7 +15,6 @@
margin-left: 12px;
}
.choose-btn {
margin-top: 8px;
margin-bottom: 12px;
}
.paper-info-tip {
......@@ -60,12 +62,16 @@
}
}
.ant-table tbody tr {
&:last-child {
&:nth-child(even) {
background: #fff;
}
&:nth-child(odd) {
background: #fafafa;
}
td {
border-bottom: none!important;
}
}
}
.ant-table-thead>tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan]) {
&::before {
......
.paper-list {
.select-box {
display: flex;
align-items: center;
.select-container{
margin-right: 24px;
.con{
background: #E9EFFF;
border-radius: 4px;
padding: 3px 16px;
display: inline-flex;
align-items: center;
justify-content: space-between;
.tip{
font-size:14px;
color:#2966FF;
margin-right:8px;
}
.text{
font-size:14px;
color:#666;
margin-right:30px;
}
.clear{
color:#5289FA;
font-size:14px;
}
}
}
}
.ant-radio-wrapper{
left: -10px;
}
......@@ -45,7 +74,7 @@
}
.paper-list-content {
position: relative;
margin-top: 16px;
margin-top: 12px;
.empty-list-tip {
color: #2966FF;
cursor: pointer;
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-05-20 11:43:21
* @LastEditTime: 2021-05-30 18:17:05
* @Description: 助学工具-新建试卷-选择题目列表
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -16,6 +16,7 @@ import {
Select,
Tooltip,
message,
DatePicker,
} from "antd";
import { PageControl } from "@/components";
import "./SelectQuestionList.less";
......@@ -23,8 +24,10 @@ import User from "@/common/js/user";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import _ from "underscore";
import Bus from "@/core/bus";
import moment from 'moment';
const { Search } = Input;
const { RangePicker } = DatePicker;
const questionTypeEnum = {
SINGLE_CHOICE: "单选题",
......@@ -68,12 +71,15 @@ class SelectQuestionList extends Component {
categoryId: null, // 当前题库分类Id
questionName: null, // 题目名称
questionType: null, // 题目类型
updateDateStart: null,
updateDateEnd: null,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
},
dataSource: [],
selectQuestionKeys: [],
expandFilter: false,
};
}
......@@ -98,6 +104,8 @@ class SelectQuestionList extends Component {
order: "UPDATED_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
updateDateStart: null,
updateDateEnd: null,
};
this.setState({ query: _query }, () => {
this.queryQuestionPageListWithContent();
......@@ -134,6 +142,8 @@ class SelectQuestionList extends Component {
order: "UPDATED_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
updateDateStart: null,
updateDateEnd: null,
};
this.setState({ query: _query }, () => {
this.queryQuestionPageListWithContent();
......@@ -144,6 +154,14 @@ class SelectQuestionList extends Component {
parseColumns = () => {
const columns = [
{
title: "题型",
key: "questionTypeEnum",
dataIndex: "questionTypeEnum",
render: (val) => {
return questionTypeEnum[val];
},
},
{
title: "题目",
key: "questionStem",
dataIndex: "questionStem",
......@@ -170,14 +188,6 @@ class SelectQuestionList extends Component {
},
},
{
title: "题型",
key: "questionTypeEnum",
dataIndex: "questionTypeEnum",
render: (val) => {
return questionTypeEnum[val];
},
},
{
title: "正确率",
key: "accuracy",
dataIndex: "accuracy",
......@@ -215,11 +225,23 @@ class SelectQuestionList extends Component {
// 改变搜索条件
handleChangeQuery = (searchType, value) => {
const _query = this.state.query;
switch (searchType) {
case "questionName":
_query.questionName = value;
break;
case "updatedTime":
_query.updateDateStart = value && value[0]?.startOf('day').valueOf();
_query.updateDateEnd = value && value[1]?.endOf('day').valueOf();
break;
case "questionType":
_query.questionType = value;
break
}
this.setState(
{
query: {
...this.state.query,
[searchType]: value || null,
..._query,
current: 1,
},
},
......@@ -281,6 +303,7 @@ class SelectQuestionList extends Component {
total,
query,
selectQuestionKeys = [],
expandFilter,
} = this.state;
const { current, size, questionName, questionType } = query;
const rowSelection = {
......@@ -364,6 +387,17 @@ class SelectQuestionList extends Component {
})}
</Select>
</div>
<div className="search-condition__item">
<span className="search-label">更新时间:</span>
<RangePicker
value={[
query.updateDateStart ? moment(Number(query.updateDateStart)) : null,
query.updateDateEnd ? moment(Number(query.updateDateEnd)) : null
]}
onChange={(value) => {
this.handleChangeQuery("updatedTime", value)
}} />
</div>
</div>
<div className="reset-fold-area">
......
......@@ -2,19 +2,22 @@
.select-question-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
width: calc(100% - 20px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
margin-right: 3%;
width: 30%;
margin-bottom: 16px;
display: flex;
.search-label {
vertical-align: middle;
display: inline-block;
height: 32px;
line-height: 32px;
flex-shrink: 0;
}
}
}
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-03-27 11:15:03
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 13:22:10
* @LastEditTime: 2021-06-01 17:28:21
* @Description: 助学工具-试卷-预览试卷
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -284,7 +284,7 @@ class PreviewPaperModal extends Component {
footer={null}
onCancel={this.props.close}
>
{paperName && <div className="paper-title">{paperName}</div>}
{paperName && <div className="paper-title">{paperName.length > 40 ? paperName.substring(0, 40) : paperName}</div>}
{questionList && questionList.length > 0 ? (
<div className="question-list-box">
{_.map(questionList, (questionItem, questionIndex) => {
......
......@@ -8,13 +8,16 @@
flex-wrap: wrap;
&__item {
display: flex;
margin-right: 3%;
margin-bottom: 16px;
width: 30%;
.search-label {
vertical-align: middle;
display: inline-block;
height: 32px;
line-height: 32px;
flex-shrink: 0;
}
}
}
......@@ -37,12 +40,37 @@
}
}
}
.select-container{
.con {
background: #E9EFFF;
border-radius: 4px;
padding: 3px 16px;
display: inline-flex;
align-items: center;
justify-content: space-between;
.tip {
font-size: 14px;
color: #2966FF;
margin-right: 8px;
}
.text {
font-size: 14px;
color: #666;
margin-right: 30px;
}
.clear {
color: #5289FA;
font-size: 14px;
}
}
}
.data-icon {
cursor: pointer;
}
.question-list-content {
position: relative;
margin-top: 16px;
margin-top: 12px;
.empty-list-tip {
color: #2966FF;
cursor: pointer;
......
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