Commit 96b7a4c4 by yuananting

feat:题目模块联调

parent 69411c9a
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-03-03 15:13:12
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:46:54
* @LastEditTime: 2021-03-15 19:44:31
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -43,4 +43,12 @@ export function addQuestion(params: object) {
export function deleteQuestion(params: object) {
return Service.Hades("anon/hades/question/deleteQuestion", params);
}
export function queryQuestionDetails(params: object) {
return Service.Hades("anon/hades/question/queryQuestionDetails", params);
}
export function editQuestion(params: object) {
return Service.Hades("anon/hades/question/editQuestion", params);
}
\ No newline at end of file
......@@ -2,11 +2,11 @@
* @Author: yuananting
* @Date: 2021-03-11 11:34:37
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:46:28
* @LastEditTime: 2021-03-15 19:45:10
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { queryCategoryTree, addCategory, delCategory, editCategory, editCategoryTree, queryQuestionCategoryTree, addQuestion, queryQuestionPageList, deleteQuestion } from '@/data-source/questionBank/request-apis';
import { queryCategoryTree, addCategory, delCategory, editCategory, editCategoryTree, queryQuestionCategoryTree, addQuestion, queryQuestionPageList, deleteQuestion, queryQuestionDetails, editQuestion } from '@/data-source/questionBank/request-apis';
export default class QuestionBankService {
// 获取题目分类树
static queryCategoryTree(params: any) {
......@@ -52,4 +52,14 @@ export default class QuestionBankService {
static deleteQuestion(params: any) {
return deleteQuestion(params);
}
// 预览题目
static queryQuestionDetails(params: any) {
return queryQuestionDetails(params);
}
// 编辑题目
static editQuestion(params: any) {
return editQuestion(params);
}
}
\ No newline at end of file
......@@ -2,12 +2,12 @@
* @Author: yuananting
* @Date: 2021-02-25 13:46:35
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:12:14
* @LastEditTime: 2021-03-15 21:14:47
* @Description: 助学工具-题库-题目管理-新增题目
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Tabs, Button, Tooltip } from "antd";
import { Tabs, Button, Tooltip, message } from "antd";
import Breadcrumbs from "@/components/Breadcrumbs";
import ShowTips from "@/components/ShowTips";
import "./AddNewQuestion.less";
......@@ -21,7 +21,7 @@ class AddNewQuestion extends Component {
constructor(props) {
super(props);
this.state = {
activeKey: "SINGLE_CHOICE",
activeKey: getParameterByName("type") || "SINGLE_CHOICE",
// 构建题目基本结构
singleChoiceContent: defineQuestionInfo("SINGLE_CHOICE"), // 单选题
multiChoiceContent: defineQuestionInfo("MULTI_CHOICE"), // 多选题
......@@ -31,34 +31,97 @@ class AddNewQuestion extends Component {
};
}
componentDidMount() {}
componentDidMount() {
if (getParameterByName("id")) {
// 编辑
this.queryQuestionDetails();
}
}
queryQuestionDetails = () => {
let query = {
id: getParameterByName("id"),
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionDetails(query).then((res) => {
const { result = [] } = res;
const { questionTypeEnum } = result;
switch (questionTypeEnum) {
case "SINGLE_CHOICE":
this.setState({ singleChoiceContent: result });
break;
case "MULTI_CHOICE":
this.setState({ multiChoiceContent: result });
break;
case "JUDGE":
this.setState({ judgeContent: result });
break;
case "GAP_FILLING":
this.setState({ gapFillingContent: result });
break;
case "INDEFINITE_CHOICE":
this.setState({ indefiniteChoiceContent: result });
break;
}
});
};
saveCurrentQuestion = (content) => {
content.questionStemList.map((item, index)=> {
content.questionStemList.map((item, index) => {
item.sort = index;
return item;
})
content.optionList.map((item)=> {
});
content.optionList.map((item) => {
item.questionOptionContentList.map((childItem, childIndex) => {
childItem.sort = childIndex;
return childItem;
})
});
return item;
})
content.questionAnswerDescList.map((item, index)=> {
});
content.questionAnswerDescList.map((item, index) => {
item.sort = index;
return item;
})
let params = {
...content,
categoryId: getParameterByName("categoryId"),
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
};
QuestionBankService.addQuestion(params).then((res) => {
console.log(res);
});
let params = {};
let categoryId = getParameterByName("categoryId");
if (getParameterByName("id")) {
params = {
...content,
id: getParameterByName("id"),
categoryId: categoryId || null,
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
QuestionBankService.editQuestion(params).then((res) => {
if (res.success) {
message.success("保存成功");
window.RCHistory.push({
pathname: `/question-bank-index?categoryId=${params.categoryId}`,
});
}
});
} else {
params = {
...content,
categoryId: getParameterByName("categoryId"),
source: 0,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
};
QuestionBankService.addQuestion(params).then((res) => {
if (res.success) {
message.success("保存成功");
window.RCHistory.push({
pathname: `/question-bank-index?categoryId=${params.categoryId}`,
});
}
});
}
};
confirmSaveQuestion = () => {
......@@ -71,12 +134,14 @@ class AddNewQuestion extends Component {
} = this.state;
switch (this.state.activeKey) {
case "SINGLE_CHOICE":
if (this.singleRef.checkInput() === 0) {
if (this.singleChoiceRef.checkInput() === 0) {
this.saveCurrentQuestion(singleChoiceContent);
}
break;
case "MULTI_CHOICE":
this.multipleRef.checkInput();
if (this.multiChoiceRef.checkInput() === 0) {
this.saveCurrentQuestion(multiChoiceContent);
}
break;
case "JUDGE":
this.judgeRef.checkInput();
......@@ -85,19 +150,21 @@ class AddNewQuestion extends Component {
this.CompletionRef.checkInput();
break;
case "INDEFINITE_CHOICE":
this.indefiniteRef.checkInput();
if (this.indefiniteRef.checkInput() === 0) {
this.saveCurrentQuestion(indefiniteChoiceContent);
}
break;
}
};
handleLogger = (en, cn) => {
const { onLogger } = this.props;
onLogger && onLogger(en, cn);
};
render() {
const {
activeKey,
singleContent,
multipleContent,
judgeContent1,
completionContent,
indefiniteContent,
singleChoiceContent,
multiChoiceContent,
judgeContent,
......@@ -106,7 +173,10 @@ class AddNewQuestion extends Component {
} = this.state;
return (
<div className="page add-new-question">
<Breadcrumbs navList="新增题目" goBack={() => RCHistory.goBack()} />
<Breadcrumbs
navList={getParameterByName("id") ? "编辑题目" : "新增题目"}
goBack={() => RCHistory.goBack()}
/>
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" />
......@@ -125,13 +195,13 @@ class AddNewQuestion extends Component {
<NewQuestionTab
questionTypeKey="SINGLE_CHOICE"
onRef={(ref) => {
this.singleRef = ref;
this.singleChoiceRef = ref;
}}
questionInfo={singleChoiceContent}
onSetState={(newContent) => {
console.log(newContent);
Object.assign(singleChoiceContent, newContent);
}}
onLogger={this.handleLogger}
/>
</TabPane>
<TabPane
......@@ -141,12 +211,13 @@ class AddNewQuestion extends Component {
<NewQuestionTab
questionTypeKey="MULTI_CHOICE"
onRef={(ref) => {
this.multipleRef = ref;
this.multiChoiceRef = ref;
}}
questionInfo={multiChoiceContent}
onSetState={(newContent) => {
Object.assign(multiChoiceContent, newContent);
}}
onLogger={this.handleLogger}
/>
</TabPane>
<TabPane
......@@ -199,6 +270,7 @@ class AddNewQuestion extends Component {
onSetState={(newContent) => {
Object.assign(indefiniteChoiceContent, newContent);
}}
onLogger={this.handleLogger}
/>
</TabPane>
</Tabs>
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-21 17:51:01
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 16:48:23
* @LastEditTime: 2021-03-15 14:46:12
* @Description: 助学工具-题库-题库主页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -10,7 +10,7 @@
import React, { Component } from "react";
import "./QuestionBankIndex.less";
import QuestionBankSider from "./components/QuestionBankSider";
import QuestionManageList from "./components/QuestionManageContent";
import QuestionManageContent from "./components/QuestionManageContent";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
......@@ -25,10 +25,7 @@ class QuestionBankIndex extends Component {
};
}
componentDidMount() {
// TODO
// 接口请求 初始化数据
}
componentDidMount() {}
getCategoryIdFromSider = (selectedCategoryId) => {
if (selectedCategoryId && selectedCategoryId.length > 0) {
......@@ -36,6 +33,10 @@ class QuestionBankIndex extends Component {
}
};
updatedSiderTreeFromList = (updatedCategoryId) => {
this.setState({updatedCategoryId});
};
render() {
return (
<div className="question-bank-index page">
......@@ -44,10 +45,14 @@ class QuestionBankIndex extends Component {
<div className="sider">
<QuestionBankSider
getSelectedCategoryId={this.getCategoryIdFromSider.bind(this)}
updatedCategoryId={this.state.updatedCategoryId}
/>
</div>
<div className="content">
<QuestionManageList selectedCategoryId={this.state.selectedCategoryId} />
<QuestionManageContent
updatedSiderTree={this.updatedSiderTreeFromList.bind(this)}
selectedCategoryId={this.state.selectedCategoryId}
/>
</div>
</div>
</div>
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-23 18:28:50
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 19:53:48
* @LastEditTime: 2021-03-15 15:14:10
* @Description: 助学工具-题库-主页面分类管理
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -23,7 +23,6 @@ import {
Modal,
} from "antd";
import ShowTips from "@/components/ShowTips";
import user from "@/common/js/user";
const { DirectoryTree } = Tree;
const { Search } = Input;
const { confirm } = Modal;
......@@ -34,7 +33,7 @@ class QuestionCategoryManage extends Component {
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
treeData: [],
treeMap: {},
selectedKeys: ["0"]
selectedKeys: ["0"],
};
}
......@@ -47,12 +46,12 @@ class QuestionCategoryManage extends Component {
let query = {
source: 0,
categoryName,
userId: User.getUserId(),
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryCategoryTree(query).then((res) => {
const { result = [] } = res;
const defaultNode = { id: "0", categoryName:"未分类", categoryCount: 0}
const defaultNode = { id: "0", categoryName: "未分类", categoryCount: 0 };
result.unshift(defaultNode);
this.setState({ treeData: this.renderTreeNodes(result, categoryName) });
this.setState({
......@@ -78,7 +77,7 @@ class QuestionCategoryManage extends Component {
categoryId: item.id,
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
userId: User.getStoreUserId(),
};
QuestionBankService.delCategory(params).then((res) => {
if (res.success) {
......@@ -168,7 +167,11 @@ class QuestionCategoryManage extends Component {
onDrop = (info) => {
// 未分类不可以拖拽
if (info.dragNode.categoryName === "未分类" || info.node.categoryName === "未分类") return;
if (
info.dragNode.categoryName === "未分类" ||
info.node.categoryName === "未分类"
)
return;
// 不允许其他节点拖拽到未分类之前
if (
info.node.categoryName === "未分类" &&
......@@ -209,7 +212,11 @@ class QuestionCategoryManage extends Component {
});
if (sameNameNodes.length > 0) {
sufIndex++;
return getSuf(originCategoryName + `(${sufIndex})`, originCategoryName, sufIndex);
return getSuf(
originCategoryName + `(${sufIndex})`,
originCategoryName,
sufIndex
);
}
}
return sufIndex;
......@@ -225,9 +232,12 @@ class QuestionCategoryManage extends Component {
item.originCategoryName = item.categoryName;
}
info.dragNode.categoryName = item.originCategoryName;
let sufIndex = getSuf(info.dragNode.categoryName, item.originCategoryName, 0);
item.categoryName =
item.categoryName + (sufIndex ? `(${sufIndex})` : "");
let sufIndex = getSuf(
info.dragNode.categoryName,
item.originCategoryName,
0
);
item.categoryName = item.categoryName + (sufIndex ? `(${sufIndex})` : "");
item.categoryName =
item.originCategoryName + (sufIndex ? `(${sufIndex})` : "");
dragObj = item;
......@@ -267,7 +277,7 @@ class QuestionCategoryManage extends Component {
categoryList: newTreeData,
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
userId: User.getStoreUserId(),
};
QuestionBankService.editCategoryTree(params).then((res) => {
this.queryCategoryTree();
......@@ -430,14 +440,20 @@ class QuestionCategoryManage extends Component {
/** 树状选中事件 */
onSelect = (selectedKeys) => {
this.setState({ selectedKeys });
// TODO调用查询题目接口
};
render() {
const { treeData, expandedKeys, selectedKeys } = this.state;
return (
<div className="page question-category-manage">
<Breadcrumbs navList="课程分类" goBack={() => RCHistory.goBack()} />
<Breadcrumbs
navList="课程分类"
goBack={() =>
window.RCHistory.push({
pathname: "/question-bank-index",
})
}
/>
<div className="box">
<div className="search-condition">
<span className="search-label">搜索名称:</span>
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 14:34:29
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:09:55
* @LastEditTime: 2021-03-15 21:12:14
* @Description: 助学工具-题库-题目管理-新建题目Tab
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -22,6 +22,7 @@ import UploadingProgress from "./UploadingProgress";
import XMAudio from "./XMAudio";
import XMRecord from "./XMRecord";
import ScanFileModal from "@/modules/resource-disk/modal/ScanFileModal";
import _ from 'lodash';
class NewQuestionTab extends Component {
constructor(props) {
......@@ -93,14 +94,19 @@ class NewQuestionTab extends Component {
}
shouldComponentUpdate(nextProps, nextState) {
return (
JSON.stringify(nextProps) !== JSON.stringify(this.props) ||
JSON.stringify(nextState) !== JSON.stringify(this.state)
);
const { questionInfo } = nextProps;
if (this.props.questionInfo !== questionInfo) {
this.setState({stemContent: JSON.parse(JSON.stringify(questionInfo.questionStemList))}); // 题干内容
this.setState({chooseOptions: JSON.parse(JSON.stringify(questionInfo.optionList))}); // 单选多选不定项-选项列表
this.setState({questionAnswerDesc: JSON.parse(JSON.stringify(questionInfo.questionAnswerDescList))}); // 答案解析
this._onSetState()
}
return true;
}
_onSetState = (params = {}) => {
this.setState({ ...params, updateKey: window.random_string(16) }, () => {
this.setState({ ...params, updateKey: window.random_string(16) },
() => {
this.props.onSetState({
questionStemList: JSON.parse(JSON.stringify(this.state.stemContent)),
optionList: JSON.parse(JSON.stringify(this.state.chooseOptions)),
......@@ -108,7 +114,8 @@ class NewQuestionTab extends Component {
JSON.stringify(this.state.questionAnswerDesc)
),
});
});
}
);
};
// 保存校验
......@@ -218,7 +225,7 @@ class NewQuestionTab extends Component {
*
* @memberof QuestionInputItem
*/
handleDelOption = (optionIndex) => {
handleDelOption = (optionIndex) => {
const { chooseOptions } = this.state;
this.handleLogger("delete_option", "删除选项");
if (chooseOptions.length < 3) {
......@@ -246,7 +253,8 @@ class NewQuestionTab extends Component {
*
* @memberof QuestionInputItem
*/
handleChangeMedia = (key, uploadItemTarget, inputType) => {
handleChangeMedia = (key, uploadItemTarget, contentType) => {
this.setState({ contentType })
const { mediaFirstType } = this.state;
const mediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type !== "RICH_TEXT";
......@@ -254,11 +262,12 @@ class NewQuestionTab extends Component {
if (mediaArr.length === 0) {
this.setState({ mediaFirstType: key });
}
if (mediaArr.length > 0 && key !== mediaFirstType) {
return message.warning("只能添加1种类型的多媒体文件");
}
switch (inputType) {
case "stem":
switch (contentType) {
case "QUESTION_STEM":
case "QUESTION_OPTION":
if (mediaArr.length > 0 && key !== mediaFirstType) {
return message.warning("只能添加1种类型的多媒体文件");
}
if (mediaFirstType === "PICTURE" && mediaArr.length > 8) {
return message.warning("只能添加9张图片");
}
......@@ -269,23 +278,47 @@ class NewQuestionTab extends Component {
return message.warning("只能添加3个录音");
}
break;
case "options":
if (mediaArr.length > 0) {
return message.warning("只能添加1个多媒体文件");
case "QUESTION_ANSWER_DESC":
var existType = [];
const pictureMediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type === "PICTURE";
});
const voiceMediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type === "VOICE";
});
const recordMediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type === "RECORD";
});
const videodMediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type === "VIDEO";
});
if (pictureMediaArr.length > 0) {
existType.push("PICTURE")
}
break;
case "analysis":
if (mediaFirstType === "PICTURE" && mediaArr.length > 8) {
return message.warning("只能添加9张图片");
if (voiceMediaArr.length > 0) {
existType.push("VOICE")
}
if (mediaFirstType === "VOICE" && mediaArr.length > 2) {
return message.warning("只能添加3个音频");
if (recordMediaArr.length > 0) {
existType.push("RECORD")
}
if (mediaFirstType === "RECORD" && mediaArr.length > 2) {
return message.warning("只能添加3个录音");
if (videodMediaArr.length > 0) {
existType.push("VIDEO")
}
if (mediaFirstType === "VIDEO" && mediaArr.length > 2) {
return message.warning("只能添加3个视频");
if (existType.length > 2 && !existType.includes(key)) {
return message.warning("只能添加3种类型的多媒体文件");
} else {
if (key === "PICTURE" && pictureMediaArr.length > 8) {
return message.warning("只能添加9张图片");
}
if (key === "VOICE" && voiceMediaArr.length > 2) {
return message.warning("只能添加3个音频");
}
if (key === "RECORD" && recordMediaArr.length > 2) {
return message.warning("只能添加3个录音");
}
if (key === "VIDEO" && videodMediaArr.length > 2) {
return message.warning("只能添加3个视频");
}
}
break;
default:
......@@ -320,7 +353,7 @@ class NewQuestionTab extends Component {
};
async uploadFile(event) {
const { fileType, uploadItemTarget } = this.state;
const { fileType, uploadItemTarget, contentType } = this.state;
const mediaFile = event.target.files[0];
if (!mediaFile) return;
if (fileType === "VOICE") {
......@@ -368,7 +401,7 @@ class NewQuestionTab extends Component {
const originType = originArr[originArr.length - 1];
const uploadObj = {
contentType: "QUESTION_STEM",
contentType,
type: fileType,
contentName: `${window.random_string(16)}.${originType}`, // 文件名
fileType: originType, // 文件后缀
......@@ -455,7 +488,7 @@ class NewQuestionTab extends Component {
});
this.setState({ blanksList: _blanksList });
};
renderCompletionAnswer = (optionItem, optionIndex) => {
return (
<div className="completion-answer-box">
......@@ -533,7 +566,7 @@ class NewQuestionTab extends Component {
contentList,
placehold,
mediaBtn,
inputType,
contentType,
validateStatus
) => {
const isCompletion = this.props.questionTypeKey === "COMPLETION";
......@@ -541,10 +574,23 @@ class NewQuestionTab extends Component {
contentList,
(contentItem) => contentItem.type === "RICH_TEXT"
);
const pictureMediaList = _.filter(contentList, (mediaItem) => {
return mediaItem.type === "PICTURE";
});
const voiceMediaList = _.filter(contentList, (mediaItem) => {
return mediaItem.type === "VOICE";
});
const recordMediaList = _.filter(contentList, (mediaItem) => {
return mediaItem.type === "RECORD";
});
const videoMediaList = _.filter(contentList, (mediaItem) => {
return mediaItem.type === "VIDEO";
});
return (
<React.Fragment>
<div>
{isCompletion && inputType === "stem" ? (
{isCompletion && contentType === "QUESTION_STEM" ? (
<CompletionStem
placehold={placehold}
validateStatus={validateStatus}
......@@ -552,7 +598,7 @@ class NewQuestionTab extends Component {
mediaBtn={mediaBtn}
changeBlankCount={this.changeBlankCount.bind(this)}
onUploadMedia={(key) => {
this.handleChangeMedia(key, contentList, inputType);
this.handleChangeMedia(key, contentList, contentType);
}}
/>
) : (
......@@ -560,7 +606,7 @@ class NewQuestionTab extends Component {
markKey={this.markKey}
placehold={placehold}
validateStatus={validateStatus}
detailInfo={contentList}
detailInfo={editorContent}
mediaBtn={mediaBtn}
bindChangeContent={(cb, textElemId) => {
this.setState({ textElemId });
......@@ -572,99 +618,299 @@ class NewQuestionTab extends Component {
this._onSetState();
}}
onUploadMedia={(key) => {
this.handleChangeMedia(key, contentList, inputType);
this.handleChangeMedia(key, contentList, contentType);
}}
/>
)}
</div>
{_.map(contentList, (contentItem, index) => {
const { type, content, status } = contentItem;
let dom = "";
if (["init", "fail"].includes(status)) {
return (
<div className="mt12" key={index}>
<UploadingProgress
fileDesc={contentItem}
canCancelUpload
onReupload={() => this.handleReupload(contentItem)}
onAbort={() => this.handleAbort(contentItem, index)}
/>
</div>
);
}
switch (type) {
case "PICTURE":
dom = (
<div className="picture-box">
<img
className="img-box"
src={content}
onClick={() => this.handleScanFile("JPG", content)}
/>
</div>
);
break;
case "VOICE":
dom = (
<div className="audio-box">
<XMAudio
forbidParse
url={contentItem.content}
getDuration={(size) => {
contentItem.size = size;
this.setState({});
}}
index={index}
size={contentItem.size || 1000}
/>
</div>
);
break;
case "VIDEO":
dom = (
<div
className="video-box"
onClick={() => this.handleScanFile("MP4", content)}
>
<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"
{contentType === "QUESTION_ANSWER_DESC" ?
<div className="question-desc-box">
{pictureMediaList.length > 0 && <div className="desc-picture-box">
{_.map(pictureMediaList, (pictureItem, pictureIndex) => {
let { content, status } = pictureItem;
if (["init", "fail"].includes(status)) {
return (
<div className="mt12" key={pictureIndex}>
<UploadingProgress
fileDesc={pictureItem}
canCancelUpload
onReupload={() => this.handleReupload(pictureItem)}
onAbort={() => this.handleAbort(pictureItem, pictureIndex)}
/>
</div>
);
} else {
return (
<div className="picture-box" key={pictureIndex}>
<img
className="img-box"
src={content}
onClick={() => this.handleScanFile("JPG", content)}
/>
<span
className="icon_arrow iconfont"
onClick={() => {
contentList.map((item, index) => {
if (item.contentName === pictureItem.contentName) {
contentList.splice(index, 1);
return item
}
})
this._onSetState();
}}
>
&#xe717;
</span>
</div>
)
}
})}
</div>}
{recordMediaList.length > 0 && <div className="desc-audio-box">
{_.map(recordMediaList, (recordItem, recordIndex) => {
let { content, status, size } = recordItem;
if (["init", "fail"].includes(status)) {
return (
<div className="mt12" key={recordIndex}>
<UploadingProgress
fileDesc={recordItem}
canCancelUpload
onReupload={() => this.handleReupload(recordItem)}
onAbort={() => this.handleAbort(recordItem, recordIndex)}
/>
</div>
);
} else {
return (
<div className="audio-box" key={recordIndex}>
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={recordIndex}
size={size || 1000}
/>
<span
className="icon_sider iconfont"
onClick={() => {
contentList.map((item, index) => {
if (item.contentName === recordItem.contentName) {
contentList.splice(index, 1);
return item
}
})
this._onSetState();
}}
>
&#xe717;
</span>
</div>
)
}
})}
</div>}
{voiceMediaList.length > 0 && <div className="desc-audio-box">
{_.map(voiceMediaList, (voiceItem, voiceIndex) => {
let { content, status, size } = voiceItem;
if (["init", "fail"].includes(status)) {
return (
<div className="mt12" key={voiceIndex}>
<UploadingProgress
fileDesc={voiceItem}
canCancelUpload
onReupload={() => this.handleReupload(voiceItem)}
onAbort={() => this.handleAbort(voiceItem, voiceIndex)}
/>
</div>
);
} else {
return (
<div className="audio-box" key={voiceIndex}>
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={voiceIndex}
size={size || 1000}
/>
<span
className="icon_sider iconfont"
onClick={() => {
contentList.map((item, index) => {
if (item.contentName === voiceItem.contentName) {
contentList.splice(index, 1);
return item
}
})
this._onSetState();
}}
>
&#xe717;
</span>
</div>
)
}
})}
</div>}
{videoMediaList.length > 0 && <div className="desc-video-box">
{_.map(videoMediaList, (videoItem, videoIndex) => {
let { content, status } = videoItem;
if (["init", "fail"].includes(status)) {
return (
<div className="mt12" key={videoIndex}>
<UploadingProgress
fileDesc={videoItem}
canCancelUpload
onReupload={() => this.handleReupload(videoItem)}
onAbort={() => this.handleAbort(videoItem, videoIndex)}
/>
</div>
);
} else {
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={() => this.handleScanFile("MP4", content)}
/>
<span
className="icon_arrow iconfont"
onClick={() => {
contentList.map((item, index) => {
if (item.contentName === videoItem.contentName) {
contentList.splice(index, 1);
return item
}
})
this._onSetState();
}}
>
&#xe717;
</span>
</div>
)
}
})}
</div>}
</div>
: _.map(contentList, (contentItem, index) => {
const { type, content, status } = contentItem;
let dom = "";
if (["init", "fail"].includes(status)) {
return (
<div className="mt12" key={index}>
<UploadingProgress
fileDesc={contentItem}
canCancelUpload
onReupload={() => this.handleReupload(contentItem)}
onAbort={() => this.handleAbort(contentItem, index)}
/>
</div>
);
break;
}
return dom ? (
<div
className="question-item_question-content"
style={{
display: ["PICTURE", "VIDEO"].includes(type)
? "inline-flex"
: "flex",
}}
key={index}
>
{dom}
<span
className={
["PICTURE", "VIDEO"].includes(type)
? "icon_arrow iconfont"
: "icon_sider iconfont"
}
onClick={() => {
contentList.splice(index, 1);
this._onSetState();
}
switch (type) {
case "PICTURE":
dom = (
<div className="picture-box">
<img
className="img-box"
src={content}
onClick={() => this.handleScanFile("JPG", content)}
/>
</div>
);
break;
case "VOICE":
dom = (
<div className="audio-box">
<XMAudio
forbidParse
url={contentItem.content}
getDuration={(size) => {
contentItem.size = size;
this.setState({});
}}
index={index}
size={contentItem.size || 1000}
/>
</div>
);
break;
case "RECORD":
dom = (
<div className="audio-box">
<XMAudio
forbidParse
url={contentItem.content}
getDuration={(size) => {
contentItem.size = size;
this.setState({});
}}
index={index}
size={contentItem.size || 1000}
/>
</div>
);
break;
case "VIDEO":
dom = (
<div
className="video-box"
onClick={() => this.handleScanFile("MP4", content)}
>
<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"
/>
</div>
);
break;
}
return dom ? (
<div
className="question-item_question-content"
style={{
display: ["PICTURE", "VIDEO"].includes(type)
? "inline-grid"
: "flex",
}}
key={index}
>
&#xe717;
{dom}
<span
className={
["PICTURE", "VIDEO"].includes(type)
? "icon_arrow iconfont"
: "icon_sider iconfont"
}
onClick={() => {
contentList.splice(index, 1);
this._onSetState();
}}
>
&#xe717;
</span>
</div>
) : null;
})}
</div>
) : null;
})}
</React.Fragment>
);
};
......@@ -723,10 +969,10 @@ class NewQuestionTab extends Component {
* @memberof QuestionInputItem
*/
handleFinishRecord = (mp3URL, duration) => {
const { uploadItemTarget } = this.state;
const { uploadItemTarget, contentType } = this.state;
uploadItemTarget.push({
contentType: "VOICE",
type: "VOICE",
contentType,
type: "RECORD",
content: mp3URL,
size: duration,
});
......@@ -784,7 +1030,7 @@ class NewQuestionTab extends Component {
stemContent,
placehold,
["VOICE", "RECORD", "PICTURE"],
"stem",
"QUESTION_STEM",
stemValidate
)}
</Form.Item>
......@@ -828,62 +1074,62 @@ class NewQuestionTab extends Component {
>
{isJudge
? _.map(judgeOptions, (optionItem, optionIndex) => {
return (
<div className="question-item_options__content">
<div className="question-item_options__setting">
<Form.Item
validateStatus={radioValidate}
help={
optionIndex === judgeOptions.length - 1
? radioText
: ""
}
>
{/* 判断 */}
<Radio
checked={optionItem.isCorrectAnswer === 1}
onClick={() => {
_.each(judgeOptions, (item) => {
item.isCorrectAnswer = 0;
});
optionItem.isCorrectAnswer = 1;
this._onSetState();
}}
/>
</Form.Item>
</div>
<div className="question-item_options__sort mr12">
{NUM_TO_WORD_MAP[optionIndex]}.
</div>
<div className="question-item_options__input">
{optionItem.label}
</div>
return (
<div className="question-item_options__content">
<div className="question-item_options__setting">
<Form.Item
validateStatus={radioValidate}
help={
optionIndex === judgeOptions.length - 1
? radioText
: ""
}
>
{/* 判断 */}
<Radio
checked={optionItem.isCorrectAnswer === 1}
onClick={() => {
_.each(judgeOptions, (item) => {
item.isCorrectAnswer = 0;
});
optionItem.isCorrectAnswer = 1;
this._onSetState();
}}
/>
</Form.Item>
</div>
<div className="question-item_options__sort mr12">
{NUM_TO_WORD_MAP[optionIndex]}.
</div>
);
})
<div className="question-item_options__input">
{optionItem.label}
</div>
</div>
);
})
: _.map(chooseOptions, (optionItem, optionIndex) => {
const {
questionOptionContentList,
isCorrectAnswer,
} = optionItem;
optionItem.optionSort = optionIndex;
const mediaBtn = ["VOICE", "RECORD", "PICTURE"];
const placeHold =
"必填(1000字以内,可粘贴小图;可以不输入文字,只添加音频或图片)";
return (
<div className="question-item_options__content" key={optionIndex}>
<div className="question-item_options__setting">
<Form.Item
validateStatus={radioValidate}
help={
optionIndex === chooseOptions.length - 1
? radioText
: ""
}
>
{/* 单选 */}
{this.props.questionTypeKey ===
"SINGLE_CHOICE" && (
const {
questionOptionContentList,
isCorrectAnswer,
} = optionItem;
optionItem.optionSort = optionIndex;
const mediaBtn = ["VOICE", "RECORD", "PICTURE"];
const placeHold =
"必填(1000字以内,可粘贴小图;可以不输入文字,只添加音频或图片)";
return (
<div className="question-item_options__content" key={optionIndex}>
<div className="question-item_options__setting">
<Form.Item
validateStatus={radioValidate}
help={
optionIndex === chooseOptions.length - 1
? radioText
: ""
}
>
{/* 单选 */}
{this.props.questionTypeKey ===
"SINGLE_CHOICE" && (
<Radio
checked={isCorrectAnswer}
onClick={() => {
......@@ -895,10 +1141,10 @@ class NewQuestionTab extends Component {
}}
/>
)}
{/* 多选 or 不定项 */}
{["INDEFINITE_SELECT", "MULTI_CHOICE"].includes(
this.props.questionTypeKey
) && (
{/* 多选 or 不定项 */}
{["INDEFINITE_CHOICE", "MULTI_CHOICE"].includes(
this.props.questionTypeKey
) && (
<Checkbox
checked={isCorrectAnswer === 1}
onChange={(e) => {
......@@ -908,66 +1154,66 @@ class NewQuestionTab extends Component {
}}
/>
)}
</Form.Item>
</div>
<div className="question-item_options__sort mr12">
{NUM_TO_WORD_MAP[optionIndex]}.
</Form.Item>
</div>
<div className="question-item_options__sort mr12">
{NUM_TO_WORD_MAP[optionIndex]}.
</div>
<div className="question-item_options__input">
<Form.Item
validateStatus={
this.state[`optionsValidate_${optionIndex}`]
<div className="question-item_options__input">
<Form.Item
validateStatus={
this.state[`optionsValidate_${optionIndex}`]
}
help={this.state[`optionsText_${optionIndex}`]}
>
{this.renderContent(
questionOptionContentList,
placeHold,
mediaBtn,
"QUESTION_OPTION",
this.state[`optionsValidate_${optionIndex}`]
)}
</Form.Item>
</div>
<div className="question-item_options__extra">
<React.Fragment>
<span
className="option-operate_item__icon icon iconfont"
onClick={() =>
this.handleDelOption(optionIndex)
}
help={this.state[`optionsText_${optionIndex}`]}
>
{this.renderContent(
questionOptionContentList,
placeHold,
mediaBtn,
"options",
this.state[`optionsValidate_${optionIndex}`]
)}
</Form.Item>
</div>
<div className="question-item_options__extra">
<React.Fragment>
&#xe81a;
</span>
{optionIndex > 0 && (
<span
className="option-operate_item__icon icon iconfont"
onClick={() =>
this.handleDelOption(optionIndex)
this.handleMoveOption(optionIndex, -1)
}
>
&#xe81a;
&#xe74a;
</span>
{optionIndex > 0 && (
<span
className="option-operate_item__icon icon iconfont"
onClick={() =>
this.handleMoveOption(optionIndex, 1)
}
>
&#xe74a;
</span>
)}
{optionIndex < chooseOptions.length - 1 && (
<span
className="option-operate_item__icon icon iconfont"
style={{
transform: "rotate(180deg)",
display: "inline-block",
}}
onClick={() =>
this.handleMoveOption(optionIndex, -1)
}
>
&#xe74a;
</span>
)}
</React.Fragment>
</div>
)}
{optionIndex < chooseOptions.length - 1 && (
<span
className="option-operate_item__icon icon iconfont"
style={{
transform: "rotate(180deg)",
display: "inline-block",
}}
onClick={() =>
this.handleMoveOption(optionIndex, 1)
}
>
&#xe74a;
</span>
)}
</React.Fragment>
</div>
);
})}
</div>
);
})}
{!isJudge && (
<div
className="question-item_options__add"
......@@ -987,7 +1233,7 @@ class NewQuestionTab extends Component {
questionAnswerDesc,
"1000字以内,可粘贴小图",
["VOICE", "RECORD", "PICTURE", "VIDEO"],
"analysis"
"QUESTION_ANSWER_DESC"
)}
</div>
</Form.Item>
......
......@@ -284,6 +284,89 @@
.question-item_analysis__content {
flex: 1;
margin-right: 187px;
.question-desc-box {
margin-top: 12px;
.desc-picture-box {
margin-bottom: 28px;
.picture-box {
position: relative;
display: inline-block;
width: 100px;
height: 100px;
overflow: hidden;
align-items: center;
justify-content: center;
padding: 12px 12px 0 0;
img {
max-width: 88px;
max-height: 88px;
border-radius: 4px;
}
.icon_arrow {
position: absolute;
top: 0px;
right: 5px;
color: #bfbfbf;
cursor: pointer;
font-size: 16px;
}
}
}
.desc-audio-box {
margin-bottom: 28px;
.audio-box {
position: relative;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.1);
padding: 10px 20px;
width: 320px;
margin-bottom: 12px;
.icon_sider {
color: #bfbfbf;
cursor: pointer;
font-size: 16px;
padding: 12px;
position: absolute;
top: 0px;
left: 320px;
}
}
}
.desc-video-box {
.video-box {
position: relative;
display: inline-block;
width: 208px;
// height: calc(208px * 9 / 16);
position: relative;
overflow: hidden;
// background-color: #000;
padding-top: 12px;
margin: 0px 12px 12px 0;
&_content {
max-width: 200px;
max-height: 200px;
border-radius: 4px;
}
&_btn {
width: 32px;
height: 32px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -8px;
margin-left: -16px;
}
.icon_arrow {
position: absolute;
top: 0px;
right: 0px;
color: #bfbfbf;
cursor: pointer;
font-size: 16px;
}
}
}
}
}
.completion-answer-box {
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-22 10:59:43
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:29:29
* @LastEditTime: 2021-03-15 15:22:13
* @Description: 助学工具-题库-题库主页面侧边栏
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -19,7 +19,7 @@ class QuestionBankSider extends Component {
constructor(props) {
super(props);
this.state = {
selectedKeys: ["0"],
selectedKeys: getParameterByName("categoryId") ? [getParameterByName("categoryId")] : ["0"],
searchValue: null,
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
ImportCourseCategory: null, // 引用课程分类模态框
......@@ -29,6 +29,15 @@ class QuestionBankSider extends Component {
componentDidMount() {
this.queryCategoryTree();
this.props.getSelectedCategoryId(getParameterByName("categoryId") ? [getParameterByName("categoryId")] : ["0"],)
}
shouldComponentUpdate(nextProps, nextState) {
const { updatedCategoryId } = nextProps;
if (this.props.updatedCategoryId !== updatedCategoryId) {
this.setState({ selectedKeys: [updatedCategoryId] }, () => this.queryCategoryTree());
}
return true;
}
/** 获取树状第一级key 设置默认展开第一项 */
......@@ -58,12 +67,12 @@ class QuestionBankSider extends Component {
let query = {
source: 0,
categoryName,
userId: User.getUserId(),
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
const defaultNode = { id: "0", categoryName:"未分类", categoryCount: 0}
const { categoryList = [], noCategoryCnt = 0 } = res.result;
const defaultNode = { id: "0", categoryName:"未分类", categoryCount: noCategoryCnt}
categoryList.unshift(defaultNode);
this.setState({ treeData: this.renderTreeNodes(categoryList, categoryName) });
this.setState({
......
......@@ -37,6 +37,7 @@ class QuestionEditor extends Component {
isShowSingleInput: true,
contentLength: 0,
errorInput: false,
detailInfo: props.detailInfo || {}
};
}
......@@ -44,6 +45,16 @@ class QuestionEditor extends Component {
this.renderEditor();
}
shouldComponentUpdate(nextProps, nextState) {
const { detailInfo } = nextProps;
if (this.props.detailInfo !== detailInfo) {
this.setState({detailInfo: nextProps.detailInfo}, () => {
this.renderEditor();
})
}
return true;
}
handleUploadMedia = (key) => {
this.props.onUploadMedia && this.props.onUploadMedia(key);
};
......@@ -72,8 +83,8 @@ class QuestionEditor extends Component {
};
renderEditor() {
const { editorId } = this.state;
const { detail = {}, detailInfo = {}, onChange, bindChangeContent } = this.props;
const { editorId, detailInfo} = this.state;
const { onChange, bindChangeContent } = this.props;
const editorRoot = new E(
`#editor${editorId}_tabbar`,
`#editor${editorId}_content`
......@@ -156,27 +167,6 @@ class QuestionEditor extends Component {
editorRoot.create();
this.editorRoot = editorRoot;
if (detail.content) {
const contentHtml = /^\<p/.test(detail.content)
? detail.content
: `<p>${detail.content}</p>`;
editorRoot.txt.html(detail.content);
const textLength = editorRoot.txt.text().replace(/\&nbsp\;/gi, " ")
.length;
const imgLength = contentHtml.match(/<img/g)
? contentHtml.match(/<img/g).length * 2
: 0;
const contentLength = imgLength + textLength;
this.setState(
{
contentLength,
visiblePlacehold: contentLength === 0 && !this.state.focusFlag,
},
() => {
onChange && onChange(contentHtml, this.state.contentLength);
}
);
}
if (detailInfo.content) {
const contentHtml = /^\<p/.test(detailInfo.content)
? detailInfo.content
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 21:04:36
* @LastEditTime: 2021-03-15 21:08:28
* @Description: 助学工具-题库-题目管理主页面列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -19,14 +19,24 @@ import {
Space,
Button,
Modal,
message,
} from "antd";
import { PageControl } from "@/components";
import "./QuestionManageContent.less";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import _ from "underscore";
import QuestionPreviewModal from "../modal/QuestionPreviewModal";
const { Search } = Input;
const questionTypeEnum = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
const questionTypeList = [
{
label: "单选题",
......@@ -63,32 +73,37 @@ class QuestionManageContent extends Component {
questionType: null, // 题目类型
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
userId: User.getStoreUserId(),
},
questionTypeList: [], // 题型列表
dataSource: [],
totalCount: 0,
QuestionPreviewModal: null, // 题目预览模态框
};
}
componentDidMount() {
this.queryQuestionPageList();
}
componentDidMount() {}
shouldComponentUpdate(nextProps, nextState) {
const { selectedCategoryId } = nextProps;
const _query = this.state.query;
if (this.props.selectedCategoryId !== selectedCategoryId) {
_query.categoryId = selectedCategoryId;
_query.questionName = null;
_query.questionType = null;
_query.current = 1;
this.setState({ query: _query }, () => this.queryQuestionPageList());
}
return true;
}
queryQuestionPageList = () => {
QuestionBankService.queryQuestionPageList(this.state.query).then((res) => {
const _query = this.state.query;
if (_query.categoryId === "0") _query.categoryId = null;
QuestionBankService.queryQuestionPageList(_query).then((res) => {
const { records = [], total = 0 } = res.result;
this.setState({ dataSource: records });
this.setState({ total });
});
};
......@@ -116,16 +131,81 @@ class QuestionManageContent extends Component {
deleteQuestion = (record) => {
let params = {
id: record.id,
categoryId: record.categoryId,
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
userId: User.getStoreUserId(),
};
QuestionBankService.deleteQuestion(params).then((res) => {
if (res.success) {
message.success("删除成功");
this.queryQuestionPageList();
this.props.updatedSiderTree(this.props.selectedCategoryId);
}
});
};
// 排序
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
let sort = null;
if (columnKey === "accuracy" && order === "ascend") {
sort = "ACCURACY_ASC";
}
console.log(params)
QuestionBankService.delCategory(params).then(res=>{
console.log(res)
})
}
if (columnKey === "accuracy" && order === "descend") {
sort = "ACCURACY_DESC";
}
if (columnKey === "updateTime" && order === "ascend") {
sort = "UPDATED_ASC";
}
if (columnKey === "updateTime" && order === "descend") {
sort = "UPDATED_DESC";
}
const _query = this.state.query;
_query.order = sort;
_query.current = 1;
this.setState({ query: _query }, () => this.queryQuestionPageList());
};
// 清空搜索条件
handleReset = () => {
const _query = {
...this.state.query,
current: 1,
order: "ACCURACY_DESC", // 排序规则
questionName: null, // 题目名称
questionType: null, // 题目类型
};
this.setState({ query: _query }, () => {
this.queryQuestionPageList();
});
};
previewQuestion = (id) => {
const m = (
<QuestionPreviewModal
id={id}
close={() => {
this.setState({
QuestionPreviewModal: null,
});
}}
/>
);
this.setState({ QuestionPreviewModal: m });
};
toEditQuetion = (id, type) => {
const { categoryId } = this.state.query;
if (categoryId) {
window.RCHistory.push({
pathname: `/create-new-question?id=${id}&type=${type}&categoryId=${categoryId}`,
});
} else {
window.RCHistory.push({
pathname: `/create-new-question?id=${id}&type=${type}`,
});
}
};
// 表头设置
parseColumns = () => {
......@@ -134,27 +214,44 @@ class QuestionManageContent extends Component {
title: "题目",
key: "questionStem",
dataIndex: "questionStem",
width: 300,
render: (val, record) => {
return <div className="record-name">{val}</div>;
var handleVal = val;
handleVal = handleVal.replace(/<(?!img).*?>/g, "");
handleVal = handleVal.replace(/<\s?img[^>]*>/gi, "【图片】");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
return (
<Tooltip title={handleVal}>
<div className="record-name">{handleVal}</div>
</Tooltip>
);
},
},
{
title: "题型",
key: "questionTypeEnum",
dataIndex: "questionTypeEnum",
render: (val) => {
return questionTypeEnum[val];
},
},
{
title: "正确率",
key: "accuracy",
dataIndex: "accuracy",
sorter: true,
sortDirections: ["ascend", "descend", "ascend"],
showSorterTooltip: false,
render: (val) => {
return val + "%";
},
},
{
title: "更新时间",
key: "updateTime",
dataIndex: "updateTime",
sorter: true,
sortDirections: ["ascend", "descend", "ascend"],
showSorterTooltip: false,
render: (val) => {
return formatDate("YYYY-MM-DD H:i:s", val);
},
......@@ -169,14 +266,14 @@ class QuestionManageContent extends Component {
<div className="record-operate">
<div
className="record-operate__item"
onClick={() => console.log("预览")}
onClick={() => this.previewQuestion(record.id)}
>
预览
</div>
<span className="record-operate__item split"> | </span>
<div
className="record-operate__item"
onClick={() => console.log("预览")}
onClick={() => this.toEditQuetion(record.id, record.questionTypeEnum)}
>
编辑
</div>
......@@ -227,9 +324,9 @@ class QuestionManageContent extends Component {
if (current == size) {
return;
}
let _query = this.props.query;
let _query = this.state.query;
_query.size = size;
this.props.onChange(_query);
this.setState({ query: _query }, () => this.queryQuestionPageList());
};
// 改变搜索条件
......@@ -250,8 +347,8 @@ class QuestionManageContent extends Component {
};
render() {
const { dataSource = [], totalCount, query } = this.state;
const { current, size, categoryId } = query;
const { dataSource = [], total, query } = this.state;
const { current, size, categoryId, questionName, questionType } = query;
return (
<div className="question-manage-content">
<div className="question-manage-filter">
......@@ -261,6 +358,7 @@ class QuestionManageContent extends Component {
<span className="search-label">题目:</span>
<Search
placeholder="搜索题目名称"
value={questionName}
style={{ width: "calc(100% - 84px)" }}
onChange={(e) => {
this.handleChangeQuery("questionName", e.target.value);
......@@ -275,6 +373,7 @@ class QuestionManageContent extends Component {
<span className="search-label">题型:</span>
<Select
placeholder="请选择题目类型"
value={questionType}
style={{ width: "calc(100% - 70px)" }}
showSearch
allowClear
......@@ -282,7 +381,7 @@ class QuestionManageContent extends Component {
option.props.children.includes(inputVal)
}
onChange={(value) => {
if(_.isEmpty(value)) {
if (_.isEmpty(value)) {
this.handleChangeQuery("questionType", value);
}
}}
......@@ -333,25 +432,29 @@ class QuestionManageContent extends Component {
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
onChange={this.handleChangeTable}
pagination={false}
bordered
onChange={this.handleChangeTable}
/>
</ConfigProvider>
{totalCount > 0 && (
{total > 0 && (
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
total={total}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.props.onChange(_query);
this.setState({ query: _query }, () =>
this.queryQuestionPageList()
);
}}
showSizeChanger={true}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
)}
{this.state.QuestionPreviewModal}
</div>
</div>
);
......
......@@ -2,11 +2,11 @@
* @Author: yuananting
* @Date: 2021-02-25 11:26:28
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 16:42:41
* @LastEditTime: 2021-03-15 09:42:30
* @Description: 助学工具-题库-题目管理右侧内容样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-manage-content {
.question-manage-content {
.question-manage-filter {
position: relative;
.search-condition {
......@@ -57,12 +57,10 @@
cursor: pointer;
}
.record-name {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
white-space: nowrap;
width: 232px;
}
.record-operate {
display: flex;
......
/*
* @Author: chenjianyu
* @Date: 2020-09-12 17:00:44
* @LastEditTime: 2021-03-13 11:19:38
* @LastEditTime: 2021-03-15 13:00:10
* @LastEditors: yuananting
* @Description: 答题模式模板
* @Copyright © 2020 杭州杰竞科技有限公司 版权所有
......@@ -73,7 +73,7 @@ export function defineQuestionInfo(questionType) {
export function defineOptionInfo() {
return {
isCorrectAnswer: false, // 是否为正确答案选项
isCorrectAnswer: 0, // 是否为正确答案选项
questionOptionContentList: [ // 选项内容
{
contentType: "QUESTION_OPTION", // 内容类型(默认选项)
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-22 17:51:28
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 14:26:31
* @LastEditTime: 2021-03-15 09:56:59
* @Description: 助学工具-题库-题库新建或编辑题库分类模态框
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -10,7 +10,6 @@ import React, { Component } from "react";
import { Modal, Form, Input, message } from "antd";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import { node } from "prop-types";
class NewEditQuestionBankCategory extends Component {
formRef = React.createRef();
constructor(props) {
......@@ -31,7 +30,7 @@ class NewEditQuestionBankCategory extends Component {
queryCategoryTree = () => {
let query = {
source: 0,
userId: User.getUserId(),
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryCategoryTree(query).then((res) => {
......@@ -47,7 +46,7 @@ class NewEditQuestionBankCategory extends Component {
let params = {
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
userId: User.getStoreUserId(),
};
if (type === "new") {
//新增
......
import React, { Component } from "react";
import { Modal, Divider } from "antd";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import "./QuestionPreviewModal.less";
import ScanFileModal from "@/modules/resource-disk/modal/ScanFileModal";
import _ from "underscore";
import XMAudio from "../components/XMAudio";
import { NUM_TO_WORD_MAP } from "@/common/constants/punchClock/punchClock";
const questionTypeList = {
SINGLE_CHOICE: "单选题",
MULTI_CHOICE: "多选题",
JUDGE: "判断题",
GAP_FILLING: "填空题",
INDEFINITE_CHOICE: "不定项选择题",
};
class QuestionPreviewModal extends Component {
formRef = React.createRef();
constructor(props) {
super(props);
this.state = {
questionInfo: {},
};
}
componentDidMount() {
this.queryQuestionDetails();
}
// 题目预览
queryQuestionDetails = () => {
let query = {
id: this.props.id,
source: 0,
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionDetails(query).then((res) => {
const { result = [] } = res;
this.setState({ questionInfo: result });
});
};
handleScanFile = (scanFileType, scanFileAddress) => {
this.setState({
showScanFile: true,
scanFileAddress,
scanFileType,
});
};
render() {
const {
showScanFile,
scanFileAddress,
scanFileType,
questionInfo,
} = this.state;
const {
questionTypeEnum,
questionStemList,
optionList,
questionAnswerDescList,
} = questionInfo;
// 查找答案选项
let rightAnswerSort = [];
_.filter(optionList, (optionItem, optionIndex) => {
if (optionItem.isCorrectAnswer === 1) {
rightAnswerSort.push(optionIndex);
}
});
const textDescList = _.filter(questionAnswerDescList, (descItem) => {
return descItem.type === "RICH_TEXT";
});
const pictureDescList = _.filter(questionAnswerDescList, (descItem) => {
return descItem.type === "PICTURE";
});
const voiceDescList = _.filter(questionAnswerDescList, (descItem) => {
return descItem.type === "VOICE";
});
const recordDescList = _.filter(questionAnswerDescList, (descItem) => {
return descItem.type === "RECORD";
});
const videoDeacList = _.filter(questionAnswerDescList, (descItem) => {
return descItem.type === "VIDEO";
});
return (
<div>
<Modal
className="question-preview-modal"
visible={true}
title="题目预览"
width={560}
footer={null}
onCancel={this.props.close}
>
<div className="question-modal-content">
<div className="question-type">
<div className="question-type__title">题型:</div>
<div className="question-type__content">
{questionTypeList[questionTypeEnum]}
</div>
</div>
<div className="question-stem">
<div className="question-stem__title">题目:</div>
<div className="question-stem__content">
{_.map(questionStemList, (item, index) => {
let dom = "";
let { type, content, size } = item;
switch (type) {
case "RICH_TEXT":
dom = (
<div
key={index}
className="input-box"
dangerouslySetInnerHTML={{
__html: content,
}}
/>
);
break;
case "PICTURE":
dom = (
<div key={index} className="picture-box">
<img
src={content}
onClick={() => this.handleScanFile("JPG", content)}
/>
</div>
);
break;
case "VOICE":
dom = (
<div key={index} className="voice-box">
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={index}
size={size || 1000}
/>
</div>
);
break;
}
return dom;
})}
</div>
</div>
<hr style={{ margin: "16px 0", color: "#E8E8E8", height: "1px" }} />
<div className="question-option">
<div className="question-option__title">选项:</div>
<div className="question-option__content">
{_.map(optionList, (optionItem, optionIndex) => {
const { questionOptionContentList } = optionItem;
const inputcontent = _.filter(
questionOptionContentList,
(optionItem) => {
return optionItem.type === "RICH_TEXT";
}
);
return (
<div className="option-box" key={optionIndex}>
<div className="option-box-header">
<div className="option-sort">
{NUM_TO_WORD_MAP[optionIndex]}.
</div>
<div
className="input-box"
dangerouslySetInnerHTML={{
__html: inputcontent[0].content,
}}
/>
</div>
{_.map(questionOptionContentList, (item, index) => {
let dom = "";
let { type, content, size } = item;
switch (type) {
case "PICTURE":
dom = (
<div key={index + 1} className="picture-box">
<img
src={content}
onClick={() =>
this.handleScanFile("JPG", content)
}
/>
</div>
);
break;
case "VOICE":
dom = (
<div key={index + 1} className="voice-box">
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={index}
size={size || 1000}
/>
</div>
);
break;
}
return dom;
})}
</div>
);
})}
</div>
</div>
<div className="question-answer">
<div className="question-answer__title">答案:</div>
<div className="question-answer__content">
{_.map(rightAnswerSort, (item, index) => {
return (
<div className="option-sort" key={index}>
{NUM_TO_WORD_MAP[item]}
</div>
);
})}
</div>
</div>
<hr style={{ margin: "16px 0", color: "#E8E8E8", height: "1px" }} />
<div className="question-desc">
<div className="question-desc__title">答案解析:</div>
<div className="question-desc__content">
<div className="question-desc-box">
{textDescList.length > 0 && (
<div
className="desc-input-box"
dangerouslySetInnerHTML={{
__html: textDescList[0].content,
}}
/>
)}
{pictureDescList.length > 0 && (
<div className="desc-picture-box">
{_.map(pictureDescList, (pictureItem, pictureIndex) => {
let { content } = pictureItem;
return (
<div className="picture-box" key={pictureIndex}>
<img
className="img-box"
src={content}
onClick={() =>
this.handleScanFile("JPG", content)
}
/>
</div>
);
})}
</div>
)}
{recordDescList.length > 0 && (
<div className="desc-audio-box">
{_.map(recordDescList, (recordItem, recordIndex) => {
let { content, size } = recordItem;
return (
<div className="audio-box" key={recordIndex}>
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={recordIndex}
size={size || 1000}
/>
</div>
);
})}
</div>
)}
{voiceDescList.length > 0 && (
<div className="desc-audio-box">
{_.map(voiceDescList, (voiceItem, voiceIndex) => {
let { content, size } = voiceItem;
return (
<div className="audio-box" key={voiceIndex}>
<XMAudio
forbidParse
url={content}
getDuration={(durationSize) => {
size = durationSize;
this.setState({});
}}
index={voiceIndex}
size={size || 1000}
/>
</div>
);
})}
</div>
)}
{videoDeacList.length > 0 && (
<div className="desc-video-box">
{_.map(videoDeacList, (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={() =>
this.handleScanFile("MP4", content)
}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
</Modal>
{showScanFile && (
<ScanFileModal
modalTitle={scanFileType === "MP4" ? "视频播放" : "查看大图"}
fileType={scanFileType}
item={{
ossAddress: scanFileAddress,
}}
close={() => {
this.setState({ showScanFile: false });
}}
/>
)}
</div>
);
}
}
export default QuestionPreviewModal;
.question-modal-content {
position: relative;
.question-type {
margin-bottom: 16px;
&__title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
}
&__content {
font-size: 14px;
font-weight: 400;
color: #666666;
}
}
.question-stem {
margin-bottom: 16px;
img {
max-width: 88px;
max-height: 88px;
}
&__title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
}
&__content {
font-size: 14px;
font-weight: 400;
color: #666666;
.input-box {
margin-bottom: 8px;
* {
display: inline-block;
}
}
.picture-box {
display: inline-flex;
margin: 12px 12px 0 0;
}
.voice-box {
margin-bottom: 12px;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.1);
padding: 10px 20px;
width: 320px;
}
}
}
.question-option {
margin-bottom: 16px;
img {
max-width: 88px;
max-height: 88px;
}
&__title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
}
&__content {
font-size: 14px;
font-weight: 400;
color: #666666;
.option-box {
color: #666666;
margin-bottom: 8px;
.option-box-header {
.option-sort {
display: inline-block;
margin-right: 5px;
}
.input-box {
display: inline-block;
max-width: calc(100% - 20px);
vertical-align: top;
}
}
.picture-box {
display: inline-flex;
margin: 12px 12px 0 0;
}
.voice-box {
margin-bottom: 12px;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.1);
padding: 10px 20px;
width: 320px;
}
}
}
}
.question-answer {
margin-bottom: 16px;
img {
max-width: 88px;
max-height: 88px;
}
&__title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
}
&__content {
font-size: 14px;
font-weight: 400;
color: #666666;
.option-sort {
display: inline-block;
margin-right: 8px;
}
}
}
.question-desc {
margin-bottom: 16px;
&__title {
height: 22px;
font-size: 16px;
color: #333333;
line-height: 22px;
margin-bottom: 8px;
}
&__content {
font-size: 14px;
font-weight: 400;
color: #666666;
.desc-input-box {
margin-bottom: 8px;
* {
display: inline-block;
}
}
.desc-picture-box {
display: inline-flex;
margin-bottom: 28px;
.picture-box {
width: 88px;
height: 88px;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
.img-box {
max-width: 88px;
max-height: 88px;
border-radius: 4px;
}
}
}
.desc-audio-box {
margin-bottom: 28px;
.audio-box {
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.1);
padding: 10px 20px;
width: 320px;
margin-bottom: 12px;
}
}
.desc-video-box {
.video-box {
position: relative;
display: inline-block;
width: 208px;
overflow: hidden;
padding-top: 12px;
margin: 0px 12px 12px 0;
&_content {
max-width: 200px;
max-height: 200px;
border-radius: 4px;
}
&_btn {
width: 32px;
height: 32px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -8px;
margin-left: -16px;
}
}
}
}
}
}
.question-preview-modal.ant-modal {
max-height: 60% !important;
}
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