Commit 69411c9a by yuananting

feat:初步联调

parent fea42dc1
/*
* @Author: yuananting
* @Date: 2021-03-03 15:13:12
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:46:54
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from "@/common/js/service";
export function queryCategoryTree(params: object) {
return Service.Hades("anon/hades/category/queryCategoryTree", params);
}
export function addCategory(params: object) {
return Service.Hades("anon/hades/category/addCategory", params);
}
export function delCategory(params: object) {
return Service.Hades("anon/hades/category/delCategory", params);
}
export function editCategory(params: object) {
return Service.Hades("anon/hades/category/editCategory", params);
}
export function editCategoryTree(params: object) {
return Service.Hades("anon/hades/category/editCategoryTree", params);
}
export function queryQuestionCategoryTree(params: object) {
return Service.Hades("anon/hades/question/queryCategoryTree", params);
}
export function queryQuestionPageList(params: object) {
return Service.Hades("anon/hades/question/queryQuestionPageList", params);
}
export function addQuestion(params: object) {
return Service.Hades("anon/hades/question/addQuestion", params);
}
export function deleteQuestion(params: object) {
return Service.Hades("anon/hades/question/deleteQuestion", params);
}
\ No newline at end of file
/*
* @Author: yuananting
* @Date: 2021-03-03 15:13:12
* @LastEditors: yuananting
* @LastEditTime: 2021-03-03 15:18:30
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from "@/common/js/service";
export function queryCategoryTree(params: object) {
return Service.Hades("/private/lesson/queryCategoryTree", params);
}
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-02-21 17:24:08
* @LastEditTime: 2021-03-11 11:43:59
* @LastEditors: yuananting
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
......@@ -9,7 +9,7 @@
import { MapInterface } from '@/domains/basic-domain/interface'
// 默认是 dev 环境
const ENV: string = process.env.DEPLOY_ENV || 'dev';
const ENV: string = process.env.DEPLOY_ENV || 'dev1';
console.log("process.env.DEPLOY_ENV",process)
const BASIC_HOST_MAP: MapInterface = {
dev: 'https://dev-heimdall.xiaomai5.com/',
......
/*
* @Author: yuananting
* @Date: 2021-03-11 11:34:37
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 20:46:28
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { queryCategoryTree, addCategory, delCategory, editCategory, editCategoryTree, queryQuestionCategoryTree, addQuestion, queryQuestionPageList, deleteQuestion } from '@/data-source/questionBank/request-apis';
export default class QuestionBankService {
// 获取题目分类树
static queryCategoryTree(params: any) {
return queryCategoryTree(params);
}
// 新增题目分类
static addCategory(params: any) {
return addCategory(params);
}
// 删除分类
static delCategory(params: any) {
return delCategory(params);
}
// 编辑分类
static editCategory(params: any) {
return editCategory(params);
}
// 编辑分类树(拖拽)
static editCategoryTree(params: any) {
return editCategoryTree(params);
}
// 查询分类树列表
static queryQuestionCategoryTree(params: any) {
return queryQuestionCategoryTree(params);
}
// 查询题目列表
static queryQuestionPageList(params: any) {
return queryQuestionPageList(params);
}
// 添加题目
static addQuestion(params: any) {
return addQuestion(params);
}
// 删除题目
static deleteQuestion(params: any) {
return deleteQuestion(params);
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 13:46:35
* @LastEditors: yuananting
* @LastEditTime: 2021-03-09 11:19:01
* @LastEditTime: 2021-03-13 20:12:14
* @Description: 助学工具-题库-题目管理-新增题目
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -12,40 +12,79 @@ import Breadcrumbs from "@/components/Breadcrumbs";
import ShowTips from "@/components/ShowTips";
import "./AddNewQuestion.less";
import NewQuestionTab from "./components/NewQuestionTab";
import { defineQuestionData } from "./components/model";
import { defineQuestionInfo } from "./components/model";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import User from "@/common/js/user";
const { TabPane } = Tabs;
class AddNewQuestion extends Component {
constructor(props) {
super(props);
this.state = {
activeKey: "THE_RADIO",
singleContent: defineQuestionData("THE_RADIO"), // 单选题内容
multipleContent: defineQuestionData("MULTI_SELECT"), // 多选题内容
judgeContent: defineQuestionData("JUDGE"), // 多选题内容
completionContent: defineQuestionData("COMPLETION"), // 填空题内容
indefiniteContent: defineQuestionData("INDEFINITE_SELECT"), // 不定项选择题内容
activeKey: "SINGLE_CHOICE",
// 构建题目基本结构
singleChoiceContent: defineQuestionInfo("SINGLE_CHOICE"), // 单选题
multiChoiceContent: defineQuestionInfo("MULTI_CHOICE"), // 多选题
judgeContent: defineQuestionInfo("JUDGE"), // 判断题
gapFillingContent: defineQuestionInfo("GAP_FILLING"), // 填空题
indefiniteChoiceContent: defineQuestionInfo("INDEFINITE_CHOICE"), // 不定项选择题
};
}
componentDidMount() {}
saveCurrentQuestion = () => {
saveCurrentQuestion = (content) => {
content.questionStemList.map((item, index)=> {
item.sort = index;
return item;
})
content.optionList.map((item)=> {
item.questionOptionContentList.map((childItem, childIndex) => {
childItem.sort = childIndex;
return childItem;
})
return item;
})
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);
});
};
confirmSaveQuestion = () => {
const {
singleChoiceContent,
multiChoiceContent,
judgeContent,
gapFillingContent,
indefiniteChoiceContent,
} = this.state;
switch (this.state.activeKey) {
case "THE_RADIO":
console.log(this.singleRef)
this.singleRef.checkInput();
case "SINGLE_CHOICE":
if (this.singleRef.checkInput() === 0) {
this.saveCurrentQuestion(singleChoiceContent);
}
break;
case "MULTI_SELECT":
case "MULTI_CHOICE":
this.multipleRef.checkInput();
break;
case "JUDGE":
this.judgeRef.checkInput();
break;
case "COMPLETION":
case "GAP_FILLING":
this.CompletionRef.checkInput();
break;
case "INDEFINITE_SELECT":
case "INDEFINITE_CHOICE":
this.indefiniteRef.checkInput();
break;
}
......@@ -56,9 +95,14 @@ class AddNewQuestion extends Component {
activeKey,
singleContent,
multipleContent,
judgeContent,
judgeContent1,
completionContent,
indefiniteContent,
singleChoiceContent,
multiChoiceContent,
judgeContent,
gapFillingContent,
indefiniteChoiceContent,
} = this.state;
return (
<div className="page add-new-question">
......@@ -76,31 +120,32 @@ class AddNewQuestion extends Component {
>
<TabPane
tab={<span className="icon iconfont">&#xe7fa; 单选题</span>}
key="THE_RADIO"
key="SINGLE_CHOICE"
>
<NewQuestionTab
questionTypeKey="THE_RADIO"
questionTypeKey="SINGLE_CHOICE"
onRef={(ref) => {
this.singleRef = ref;
}}
questionContent={singleContent}
questionInfo={singleChoiceContent}
onSetState={(newContent) => {
Object.assign(singleContent, newContent);
console.log(newContent);
Object.assign(singleChoiceContent, newContent);
}}
/>
</TabPane>
<TabPane
tab={<span className="icon iconfont">&#xe7fb; 多选题</span>}
key="MULTI_SELECT"
key="MULTI_CHOICE"
>
<NewQuestionTab
questionTypeKey="MULTI_SELECT"
questionTypeKey="MULTI_CHOICE"
onRef={(ref) => {
this.multipleRef = ref;
}}
questionContent={multipleContent}
questionInfo={multiChoiceContent}
onSetState={(newContent) => {
Object.assign(multipleContent, newContent);
Object.assign(multiChoiceContent, newContent);
}}
/>
</TabPane>
......@@ -113,7 +158,7 @@ class AddNewQuestion extends Component {
onRef={(ref) => {
this.judgeRef = ref;
}}
questionContent={judgeContent}
questionInfo={judgeContent}
onSetState={(newContent) => {
Object.assign(judgeContent, newContent);
}}
......@@ -121,16 +166,16 @@ class AddNewQuestion extends Component {
</TabPane>
<TabPane
tab={<span className="icon iconfont">&#xe7fd; 填空题</span>}
key="COMPLETION"
key="GAP_FILLING"
>
<NewQuestionTab
questionTypeKey="COMPLETION"
questionTypeKey="GAP_FILLING"
onRef={(ref) => {
this.CompletionRef = ref;
}}
questionContent={completionContent}
questionInfo={gapFillingContent}
onSetState={(newContent) => {
Object.assign(completionContent, newContent);
Object.assign(gapFillingContent, newContent);
}}
/>
</TabPane>
......@@ -143,16 +188,16 @@ class AddNewQuestion extends Component {
</Tooltip>
</span>
}
key="INDEFINITE_SELECT"
key="INDEFINITE_CHOICE"
>
<NewQuestionTab
questionTypeKey="INDEFINITE_SELECT"
questionTypeKey="INDEFINITE_CHOICE"
onRef={(ref) => {
this.indefiniteRef = ref;
}}
questionContent={indefiniteContent}
questionInfo={indefiniteChoiceContent}
onSetState={(newContent) => {
Object.assign(indefiniteContent, newContent);
Object.assign(indefiniteChoiceContent, newContent);
}}
/>
</TabPane>
......@@ -164,7 +209,7 @@ class AddNewQuestion extends Component {
<Button
type="primary"
onClick={() => {
this.saveCurrentQuestion();
this.confirmSaveQuestion();
}}
>
保存
......
......@@ -2,27 +2,23 @@
* @Author: yuananting
* @Date: 2021-02-21 17:51:01
* @LastEditors: yuananting
* @LastEditTime: 2021-03-05 13:41:32
* @LastEditTime: 2021-03-13 16:48:23
* @Description: 助学工具-题库-题库主页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Layout } from "antd";
import "./QuestionBankIndex.less";
import QuestionBankSider from "./components/QuestionBankSider";
import QuestionManageFilter from "./components/QuestionManageFilter";
import QuestionManageOpt from "./components/QuestionManageOpt";
import QuestionManageList from "./components/QuestionManageList";
import QuestionManageList from "./components/QuestionManageContent";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
class QuestionBankIndex extends Component {
constructor(props) {
super(props);
this.state = {
query: {
current: 1,
size: 10,
},
selectedCategoryId: "",
loading: true,
dataSource: [], // 题库列表数据
totalCount: 1, // 题库数据总条数
......@@ -32,46 +28,26 @@ class QuestionBankIndex extends Component {
componentDidMount() {
// TODO
// 接口请求 初始化数据
this.getQuestionBankList();
}
getQuestionBankList = (_query = {}) => {
const query = {
...this.state.query,
..._query,
};
// 更新请求参数
this.setState({ query });
// CourseService.videoSchedulePage(query).then((res) => {
// const { result = {} } = res || {};
// const { records = [], total = 0 } = result;
// this.setState({
// dataSource: records,
// totalCount: Number(total)
// });
// });
getCategoryIdFromSider = (selectedCategoryId) => {
if (selectedCategoryId && selectedCategoryId.length > 0) {
this.setState({ selectedCategoryId: selectedCategoryId[0] });
}
};
render() {
const { query, dataSource, totalCount } = this.state;
return (
<div className="question-bank-index page">
<div className="content-header">题目</div>
<div className="box content-body">
<div className="sider">
<QuestionBankSider />
<QuestionBankSider
getSelectedCategoryId={this.getCategoryIdFromSider.bind(this)}
/>
</div>
<div className="content">
<QuestionManageFilter />
<QuestionManageOpt />
<QuestionManageList
query={query}
dataSource={dataSource}
totalCount={totalCount}
onChange={this.getQuestionList}
/>
<QuestionManageList selectedCategoryId={this.state.selectedCategoryId} />
</div>
</div>
</div>
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-21 18:27:43
* @LastEditors: yuananting
* @LastEditTime: 2021-03-02 17:26:30
* @LastEditTime: 2021-03-11 19:10:44
* @Description: 助学工具-题库-题库主页面样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -18,6 +18,7 @@
.content {
width: 100%;
margin-left: 24px;
height: calc(100vh - 160px);
}
}
}
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-23 18:28:50
* @LastEditors: yuananting
* @LastEditTime: 2021-03-05 10:19:34
* @LastEditTime: 2021-03-13 19:53:48
* @Description: 助学工具-题库-主页面分类管理
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -10,153 +10,88 @@ import React, { Component } from "react";
import Breadcrumbs from "@/components/Breadcrumbs";
import "./QuestionCategoryManage.less";
import NewEditQuestionBankCategory from "./modal/NewEditQuestionBankCategory";
import { Tree, Input, Space, Button, Menu, Dropdown, message } from "antd";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
import User from "@/common/js/user";
import {
Tree,
Input,
Space,
Button,
Menu,
Dropdown,
message,
Modal,
} from "antd";
import ShowTips from "@/components/ShowTips";
const { DirectoryTree, Popconfirm } = Tree;
import user from "@/common/js/user";
const { DirectoryTree } = Tree;
const { Search } = Input;
const { confirm } = Modal;
class QuestionCategoryManage extends Component {
constructor(props) {
super(props);
this.state = {
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
treeData: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023655839768500",
parentId: "0",
rootId: "1367023655839768500",
categoryLevel: 0,
categoryName: "未分类",
sort: 0,
},
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023655839768577",
parentId: "0",
rootId: "1367023655839768577",
categoryLevel: 0,
categoryName: "分类1",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023709942095873",
parentId: "1367023655839768577",
rootId: "1367023655839768577",
categoryLevel: 1,
categoryName: "test",
sort: 0,
},
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023725184196609",
parentId: "1367023655839768577",
rootId: "1367023655839768577",
categoryLevel: 1,
categoryName: "分类2",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023805563838466",
parentId: "1367023725184196609",
rootId: "1367023655839768577",
categoryLevel: 2,
categoryName: "test",
sort: 0,
},
],
sort: 1,
},
],
sort: 1,
},
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367049864988516353",
parentId: "0",
rootId: "1367049864988516353",
categoryLevel: 0,
categoryName: "分类2",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367049931656978433",
parentId: "1367049864988516353",
rootId: "1367049864988516353",
categoryLevel: 1,
categoryName: "分类2",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367049973746819074",
parentId: "1367049931656978433",
rootId: "1367049864988516353",
categoryLevel: 2,
categoryName: "分类3",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367050021431861249",
parentId: "1367049973746819074",
rootId: "1367049864988516353",
categoryLevel: 3,
categoryName: "分类4",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367050063035162626",
parentId: "1367050021431861249",
rootId: "1367049864988516353",
categoryLevel: 4,
categoryName: "分类5",
sort: 0,
},
],
sort: 0,
},
],
sort: 0,
},
],
sort: 0,
},
],
sort: 2,
},
],
treeData: [],
treeMap: {},
selectedKeys: ["0"]
};
}
componentDidMount() {
this.setState({ treeData: this.renderTreeNodes(this.state.treeData) });
this.setState({
expandedKeys: this.getFirstLevelKeys(this.state.treeData),
});
this.queryCategoryTree();
}
// 新增分类
newEditQuestionCategory = (categoryType, id, parentId, addLevelType) => {
// 查询分类树
queryCategoryTree = (categoryName) => {
let query = {
source: 0,
categoryName,
userId: User.getUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryCategoryTree(query).then((res) => {
const { result = [] } = res;
const defaultNode = { id: "0", categoryName:"未分类", categoryCount: 0}
result.unshift(defaultNode);
this.setState({ treeData: this.renderTreeNodes(result, categoryName) });
this.setState({
expandedKeys: this.getFirstLevelKeys(result),
});
});
};
// 删除分类
delCategory = (item) => {
return confirm({
title: "确认删除该分类吗?",
content:
"此分类下存在关联项目,如删除,此分类下包含的所有内容将自动转入“未分类”中。",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
let params = {
categoryId: item.id,
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
};
QuestionBankService.delCategory(params).then((res) => {
if (res.success) {
message.success("删除分类成功");
this.queryCategoryTree();
}
});
},
});
};
// 新增或编辑分类
newEditQuestionCategory = (categoryType, addLevelType, type, node) => {
let title = "";
let label = "";
switch (categoryType) {
......@@ -179,13 +114,14 @@ class QuestionCategoryManage extends Component {
}
const m = (
<NewEditQuestionBankCategory
id={id}
parentId={parentId}
node={node}
addLevelType={addLevelType}
type={type}
treeData={this.state.treeData}
title={title}
label={label}
close={() => {
this.queryCategoryTree();
this.setState({
NewEditQuestionBankCategory: null,
});
......@@ -205,38 +141,37 @@ class QuestionCategoryManage extends Component {
item.categoryLevel === 0
? "editEqualLevelCategory"
: "editChildLevelCategory";
this.newEditQuestionCategory(
categoryType,
item.id,
item.parentId,
"equal"
);
this.newEditQuestionCategory(categoryType, "equal", "edit", item);
}}
>
重命名
</span>
</Menu.Item>
<Menu.Item key="1">
<span>删除</span>
<span
onClick={() => {
this.delCategory(item);
}}
>
删除
</span>
</Menu.Item>
</Menu>
);
};
getRelatedNodes = (parentId) => {
console.log("parentId:", parentId);
return this.state.treeMap[parentId].subList
? this.state.treeMap[parentId].subList
return this.state.treeMap[parentId]
? this.state.treeMap[parentId].sonCategoryList
: [];
};
onDrop = (info) => {
console.log("info:", info);
// 未分类不可以拖拽
if (info.dragNode.id === "1367023655839768500") return;
if (info.dragNode.categoryName === "未分类" || info.node.categoryName === "未分类") return;
// 不允许其他节点拖拽到未分类之前
if (
info.node.id === "1367023655839768500" &&
info.node.categoryName === "未分类" &&
info.dropToGap &&
info.dropPosition === -1
)
......@@ -245,14 +180,6 @@ class QuestionCategoryManage extends Component {
let targetParentId = info.dropToGap ? info.node.parentId : info.node.id;
let relatedNodes = this.getRelatedNodes(targetParentId);
console.log(
"relatedNodes:",
relatedNodes.length,
relatedNodes,
targetParentId
);
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split("-");
......@@ -264,27 +191,25 @@ class QuestionCategoryManage extends Component {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].subList) {
loop(data[i].subList, key, callback);
if (data[i].sonCategoryList) {
loop(data[i].sonCategoryList, key, callback);
}
}
};
const data = [...this.state.treeData];
let getSuf = function (name, sufIndex) {
if (relatedNodes.length > 0) {
let getSuf = function (name, originCategoryName, sufIndex) {
if (relatedNodes && relatedNodes.length > 0) {
let sameNameNodes = [];
relatedNodes.forEach((item) => {
if (item.id === info.dragNode.id) return true;
if (item.categoryName === name) {
sameNameNodes.push(item);
console.log(item, sameNameNodes);
}
});
if (sameNameNodes.length > 0) {
sufIndex++;
return getSuf(name + `(${sufIndex})`, sufIndex);
return getSuf(originCategoryName + `(${sufIndex})`, originCategoryName, sufIndex);
}
}
return sufIndex;
......@@ -300,13 +225,9 @@ class QuestionCategoryManage extends Component {
item.originCategoryName = item.categoryName;
}
info.dragNode.categoryName = item.originCategoryName;
console.log("info.dragNode.categoryName:", info.dragNode.categoryName);
let sufIndex = getSuf(info.dragNode.categoryName, 0);
console.log("sufIndex1:", sufIndex);
console.log("sufIndex2:", sufIndex > 0 ? `${sufIndex}` : "");
let sufIndex = getSuf(info.dragNode.categoryName, item.originCategoryName, 0);
item.categoryName =
item.categoryName + (sufIndex ? `(${sufIndex})` : "");
item.categoryName =
item.originCategoryName + (sufIndex ? `(${sufIndex})` : "");
dragObj = item;
......@@ -314,17 +235,17 @@ class QuestionCategoryManage extends Component {
if (!info.dropToGap) {
loop(data, dropKey, (item) => {
item.subList = item.subList || [];
item.subList.push(dragObj);
item.sonCategoryList = item.sonCategoryList || [];
item.sonCategoryList.unshift(dragObj);
});
} else if (
(info.node.props.subList || []).length > 0 &&
(info.node.props.sonCategoryList || []).length > 0 &&
info.node.props.expanded &&
dropPosition === 1
) {
loop(data, dropKey, (item) => {
item.subList = item.children || [];
item.subList.push(dragObj);
item.sonCategoryList = item.children || [];
item.sonCategoryList.unshift(dragObj);
});
} else {
let ar;
......@@ -339,17 +260,27 @@ class QuestionCategoryManage extends Component {
ar.splice(i + 1, 0, dragObj);
}
}
console.log("data:", data)
this.setState({ treeData: this.renderTreeNodes(this.handleLoop(data, 0)) });
data.shift();
let newTreeData = this.renderTreeNodes(this.handleLoop(data, 0));
this.setState({ treeData: newTreeData });
let params = {
categoryList: newTreeData,
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
};
QuestionBankService.editCategoryTree(params).then((res) => {
this.queryCategoryTree();
});
};
handleLoop = (data, level) => {
data.map((item, index) => {
item.categoryLevel = level;
item.sort = index;
if (item.children) {
this.handleLoop(item.children, level + 1);
item.categoryLevel = level;
if (item.sonCategoryList) {
item.children = this.handleLoop(item.sonCategoryList, level + 1);
item.sonCategoryList = this.handleLoop(item.sonCategoryList, level + 1);
}
return item;
});
......@@ -387,15 +318,19 @@ class QuestionCategoryManage extends Component {
className="node-title-div"
onMouseOver={(e) => {
let mouseNodeOpts = e.currentTarget.getElementsByTagName("div")[0];
mouseNodeOpts.style.visibility = "visible";
if (mouseNodeOpts) {
mouseNodeOpts.style.visibility = "visible";
}
}}
onMouseOut={(e) => {
let mouseNodeOpts = e.currentTarget.getElementsByTagName("div")[0];
mouseNodeOpts.style.visibility = "hidden";
if (mouseNodeOpts) {
mouseNodeOpts.style.visibility = "hidden";
}
}}
>
<span>{item.categoryName}</span>
{item.id !== "1367023655839768500" && (
{item.categoryName !== "未分类" && (
<Space className="title-opts" size={50}>
<span
className="icon iconfont"
......@@ -407,10 +342,14 @@ class QuestionCategoryManage extends Component {
nodesCount = treeData.length;
} else {
let parentNodes = this.getRelatedNodes(item.parentId);
nodesCount =
parentNodes.length > 0
? parentNodes[0].subList.length
: 0;
if (
parentNodes.length > 0 &&
parentNodes[0].sonCategoryList
) {
nodesCount = parentNodes[0].sonCategoryList.length;
} else {
nodesCount = 0;
}
}
if (nodesCount >= 30) {
message.info("最多只能添加30个分类");
......@@ -418,9 +357,9 @@ class QuestionCategoryManage extends Component {
}
this.newEditQuestionCategory(
"newEqualLevelCategory",
item.id,
item.parentId,
"equal"
"equal",
"new",
item
);
}}
>
......@@ -430,15 +369,18 @@ class QuestionCategoryManage extends Component {
<span
className="icon iconfont"
onClick={() => {
if (item.subList && item.subList.length >= 30) {
if (
item.sonCategoryList &&
item.sonCategoryList.length >= 30
) {
message.info("最多只能添加30个子分类");
return;
}
this.newEditQuestionCategory(
"newChildLevelCategory",
item.id,
item.parentId,
"child"
"child",
"new",
item
);
}}
>
......@@ -463,32 +405,36 @@ class QuestionCategoryManage extends Component {
</span>
);
if (item.subList) {
item.children = this.renderTreeNodes(item.subList, value);
if (item.sonCategoryList) {
item.children = this.renderTreeNodes(item.sonCategoryList, value);
}
return item;
});
this.getFirstLevelKeys(newTreeData);
// this.getFirstLevelKeys(newTreeData);
let map = {};
let getChildren = function (data) {
data.forEach((item) => {
map[item.id] = item;
if (item.subList && item.subList.length > 0) {
getChildren(item.subList);
if (item.sonCategoryList && item.sonCategoryList.length > 0) {
getChildren(item.sonCategoryList);
}
});
};
getChildren(data);
console.log("map:", map);
this.setState({ treeMap: map });
return newTreeData;
};
/** 树状选中事件 */
onSelect = (selectedKeys) => {
this.setState({ selectedKeys });
// TODO调用查询题目接口
};
render() {
const { treeData, expandedKeys } = this.state;
const { treeData, expandedKeys, selectedKeys } = this.state;
return (
<div className="page question-category-manage">
<Breadcrumbs navList="课程分类" goBack={() => RCHistory.goBack()} />
......@@ -498,12 +444,7 @@ class QuestionCategoryManage extends Component {
<Search
placeholder="请输入名称"
style={{ width: "calc(100% - 84px)" }}
onSearch={(value) =>
// TODO 调用查询分类接口
this.setState({
treeData: this.renderTreeNodes(treeData, value),
})
}
onSearch={(value) => this.queryCategoryTree(value)}
/>
</div>
<Button
......@@ -515,9 +456,8 @@ class QuestionCategoryManage extends Component {
}
this.newEditQuestionCategory(
"newEqualLevelCategory",
"1367023655839768500",
"0",
"equal"
"equal",
"new"
);
}}
>
......@@ -530,7 +470,8 @@ class QuestionCategoryManage extends Component {
<DirectoryTree
expandedKeys={expandedKeys}
onExpand={this.onExpand}
defaultSelectedKeys={["1367023655839768500"]}
selectedKeys={selectedKeys}
onSelect={this.onSelect}
draggable
blockNode
onDrop={this.onDrop}
......
/*
* @Author: yuananting
* @Date: 2021-02-25 09:45:11
* @LastEditors: yuananting
* @LastEditTime: 2021-02-25 11:35:10
* @Description: 助学工具-题库-题目管理主页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import Breadcrumbs from "@/components/Breadcrumbs";
import { Space } from "antd";
import QuestionManageFilter from "./components/QuestionManageFilter";
import QuestionManageOpt from "./components/QuestionManageOpt";
import QuestionManageList from "./components/QuestionManageList";
import "./QuestionManageIndex.less";
class QuestionManageIndex extends Component {
constructor(props) {
super(props);
this.state = {
query: {
current: 1,
size: 10,
},
loading: true,
dataSource: [], // 题目列表数据
totalCount: 1, // 题目数据总条数
};
}
componentDidMount() {
// TODO
// 接口请求 初始化数据
this.getQuestionList();
}
getQuestionList = (_query = {}) => {
const query = {
...this.state.query,
..._query,
};
// 更新请求参数
this.setState({ query });
};
render() {
const { query, dataSource, totalCount } = this.state;
return (
<div className="question-manage-index page">
<Breadcrumbs navList="课程分类" goBack={() => RCHistory.goBack()} />
<div className="box">
<Space className="question-basic-info" size={100}>
<span>题库名称:{"业务培训"}</span>
<span>题目数量:{100}</span>
</Space>
<QuestionManageFilter />
<QuestionManageOpt />
<QuestionManageList
query={query}
dataSource={dataSource}
totalCount={totalCount}
onChange={this.getQuestionList}
/>
</div>
</div>
);
}
}
export default QuestionManageIndex;
/*
* @Author: yuananting
* @Date: 2021-02-25 10:49:16
* @LastEditors: yuananting
* @LastEditTime: 2021-02-25 10:53:58
* @Description: 助学工具-题库-题目管理主页面样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-manage-index {
position: relative;
.question-basic-info {
margin-bottom: 16px;
}
}
......@@ -45,61 +45,41 @@ class CompletionStem extends Component {
}
watchStemContent = () => {
var currentBlanksList = this.saveCurrentBlanks(); // 获取当前填空列表
console.log("originBlanksList:", this.state.originBlanksList);
console.log("currentBlanksList:", currentBlanksList);
// console.log(JSON.stringify(currentBlanksList)===JSON.stringify(this.state.originBlanksList))
if (
JSON.stringify(currentBlanksList) !==
JSON.stringify(this.state.originBlanksList)
) {
console.log("修改")
this.setState({ originBlanksList: this.saveCurrentBlanks() }); // 保存修改前的填空列表
this.sortBlanks();
}
this.handleStemStyle();
this.props.changeBlankCount(currentBlanksList);
};
sortBlanks = () => {
const _blanksList = JSON.parse(JSON.stringify(this.saveCurrentBlanks()));
_blanksList.map((item, index) => {
item.innerHTML = "填空" + (index + 1);
return item;
});
this.setState({ blanksList: _blanksList });
};
saveCurrentBlanks = () => {
var currentBlanksList = [];
var stemInput = document.getElementById("editor-box_content");
var _blanksList = [];
const stemInput = document.getElementById("editor-box_content");
stemInput.childNodes.forEach((item) => {
if (item.nodeName === "SPAN") {
currentBlanksList.push(item);
_blanksList.push(item);
}
});
return currentBlanksList;
this.setState({blanksList:_blanksList})
this.handleStemStyle();
this.props.changeBlankCount(this.state.blanksList);
};
handleInsertBlanks = (id) => {
/**
* 插入占位符
*
* @memberof QuestionInputItem
*/
insertBlanks = () => {
document.getElementById("editor-box_content").focus();
const { blanksList } = this.state;
blanksList.map((item, index) => {
item.innerHTML = "填空" + (index + 1);
});
var templateBlanks = blanksList.filter((item) => item.id === id);
var insertBlanks = document.createElement("span");
insertBlanks.className = "fill-line";
insertBlanks.innerHTML = templateBlanks[0].innerHTML;
insertBlanks.id = templateBlanks[0].id; // 填空id;
insertBlanks.contentEditable = false;
const _blanksList = this.state.blanksList;
var blanks = document.createElement("span");
blanks.className = "fill-line";
blanks.innerHTML = "填空";
blanks.id = window.random_string(16); // 填空id
blanks.contentEditable = false;
_blanksList.push(blanks);
this.setState({ blanksList: _blanksList });
var sel, range;
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
var el = document.createElement("div");
el.appendChild(insertBlanks);
el.appendChild(blanks);
var frag = document.createDocumentFragment(),
node,
lastNode;
......@@ -117,24 +97,6 @@ class CompletionStem extends Component {
}
};
/**
* 插入占位符
*
* @memberof QuestionInputItem
*/
insertBlanks = () => {
const _blanksList = this.state.blanksList;
var blanks = document.createElement("span");
blanks.className = "fill-line";
blanks.innerHTML = "填空";
blanks.id = window.random_string(16); // 填空id
blanks.contentEditable = false;
_blanksList.push(blanks);
this.setState({ blanksList: _blanksList });
this.handleInsertBlanks(blanks.id);
};
// 输入框样式
handleStemStyle = () => {
const stemInput = document.getElementById("editor-box_content");
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 14:34:29
* @LastEditors: yuananting
* @LastEditTime: 2021-03-11 09:37:11
* @LastEditTime: 2021-03-13 20:09:55
* @Description: 助学工具-题库-题目管理-新建题目Tab
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -17,7 +17,7 @@ import {
NUM_TO_WORD_MAP,
QUESTION_FILE_ACCEPT,
} from "@/common/constants/punchClock/punchClock";
import { defineOptionData } from "./model";
import { defineOptionInfo } from "./model";
import UploadingProgress from "./UploadingProgress";
import XMAudio from "./XMAudio";
import XMRecord from "./XMRecord";
......@@ -26,19 +26,17 @@ import ScanFileModal from "@/modules/resource-disk/modal/ScanFileModal";
class NewQuestionTab extends Component {
constructor(props) {
super(props);
const { questionContent = {} } = props;
const { questionInfo = {} } = props;
const {
topicDescribeVOList,
itemInfoVOList,
parsingDescribeVOList,
taskModuleQuestionVO,
questionStemList,
optionList,
questionAnswerDescList,
showBox,
} = questionContent;
} = questionInfo;
this.state = {
questionDetail: JSON.parse(JSON.stringify(taskModuleQuestionVO)), // 题目其他信息
stemContent: JSON.parse(JSON.stringify(topicDescribeVOList)), // 题干内容
chooseOptions: JSON.parse(JSON.stringify(itemInfoVOList)), // 单选多选不定项-选项列表
analysisContent: JSON.parse(JSON.stringify(parsingDescribeVOList)), // 答案解析
stemContent: JSON.parse(JSON.stringify(questionStemList)), // 题干内容
chooseOptions: JSON.parse(JSON.stringify(optionList)), // 单选多选不定项-选项列表
questionAnswerDesc: JSON.parse(JSON.stringify(questionAnswerDescList)), // 答案解析
accept: QUESTION_FILE_ACCEPT["PICTURE"], // 上传媒体类型
fileType: "PICTURE", // 媒体枚举
showRecord: false, // 录音弹窗
......@@ -50,12 +48,12 @@ class NewQuestionTab extends Component {
// 判断-选项列表
label: "正确",
value: true,
ifCorrectAnswerItem: null,
isCorrectAnswer: null,
},
{
label: "错误",
value: false,
ifCorrectAnswerItem: null,
isCorrectAnswer: null,
},
],
};
......@@ -76,16 +74,56 @@ class NewQuestionTab extends Component {
this.props.onRef(this);
}
handleLogger = (en, cn) => {
const { onLogger } = this.props;
onLogger && onLogger(en, cn);
};
static getDerivedStateFromProps(nextProps, prevState) {
// 控制录音组件展示
if (nextProps.showBox && !prevState.showBox) {
return {
showRecord: false,
showBox: nextProps.showBox,
};
}
return {
showBox: nextProps.showBox,
};
}
shouldComponentUpdate(nextProps, nextState) {
return (
JSON.stringify(nextProps) !== JSON.stringify(this.props) ||
JSON.stringify(nextState) !== JSON.stringify(this.state)
);
}
_onSetState = (params = {}) => {
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)),
questionAnswerDescList: JSON.parse(
JSON.stringify(this.state.questionAnswerDesc)
),
});
});
};
// 保存校验
checkInput = () => {
let validateError = 0;
// 题干校验
const stemContent = _.find(
this.state.stemContent,
(contentItem) => contentItem.contentType === "TEXT"
(contentItem) => contentItem.type === "RICH_TEXT"
);
const stem = stemContent.content.replace(/<[^>]+>/g, "");
if (stem.length === 0) {
this.setState({ stemValidate: "error" });
this.setState({ stemText: "请输入题干" });
validateError++;
} else {
this.setState({ stemValidate: "success" });
this.setState({ stemText: "" });
......@@ -97,28 +135,30 @@ class NewQuestionTab extends Component {
const { questionTypeKey } = this.props;
if (questionTypeKey === "JUDGE") {
optionUnChecked = judgeOptions.filter(
(item) => item.ifCorrectAnswerItem === null
(item) => item.isCorrectAnswer === null
).length;
if (optionUnChecked === judgeOptions.length) {
this.setState({ radioValidate: "error" });
this.setState({ radioText: "请选择正确答案" });
validateError++;
} else {
this.setState({ radioValidate: "success" });
this.setState({ radioText: "" });
}
} else {
chooseOptions.forEach((item, index) => {
const optionContent = item.itemContentVOList;
optionUnChecked = item.questionItemVO.ifCorrectAnswerItem
const optionContent = item.questionOptionContentList;
optionUnChecked = item.isCorrectAnswer
? optionUnChecked
: optionUnChecked + 1;
if (
optionContent.length === 1 &&
optionContent[0].contentType === "TEXT" &&
optionContent[0].type === "RICH_TEXT" &&
optionContent[0].content.length === 0
) {
this.setState({ [`optionsValidate_${index}`]: "error" });
this.setState({ [`optionsText_${index}`]: "请输入选项" });
validateError++;
} else {
this.setState({ [`optionsValidate_${index}`]: "success" });
this.setState({ [`optionsText_${index}`]: "" });
......@@ -127,46 +167,21 @@ class NewQuestionTab extends Component {
if (optionUnChecked === chooseOptions.length) {
this.setState({ radioValidate: "error" });
this.setState({ radioText: "请选择正确答案" });
validateError++;
} else {
this.setState({ radioValidate: "success" });
this.setState({ radioText: "" });
}
if (
this.props.questionTypeKey === "MULTI_SELECT" &&
this.props.questionTypeKey === "MULTI_CHOICE" &&
this.state.chooseOptions.length - optionUnChecked === 1
) {
this.setState({ radioValidate: "error" });
this.setState({ radioText: "最少选两个" });
validateError++;
}
}
};
static getDerivedStateFromProps(nextProps, prevState) {
// 控制录音组件展示
if (nextProps.showBox && !prevState.showBox) {
return {
showRecord: false,
showBox: nextProps.showBox,
};
}
return {
showBox: nextProps.showBox,
};
}
_onSetState = (params = {}) => {
this.setState({ ...params, updateKey: window.random_string(16) }, () => {
this.props.onSetState({
topicDescribeVOList: JSON.parse(JSON.stringify(this.state.stemContent)),
itemInfoVOList: JSON.parse(JSON.stringify(this.state.chooseOptions)),
parsingDescribeVOList: JSON.parse(
JSON.stringify(this.state.analysisContent)
),
taskModuleQuestionVO: JSON.parse(
JSON.stringify(this.state.questionDetail)
),
});
});
return validateError;
};
/**
......@@ -192,11 +207,40 @@ class NewQuestionTab extends Component {
if (chooseOptions.length >= 20) {
message.warning("最多添加20个选项");
} else {
chooseOptions.push(defineOptionData(content));
chooseOptions.push(defineOptionInfo(content));
this._onSetState();
}
};
/**
* 删除选项
*
* @memberof QuestionInputItem
*/
handleDelOption = (optionIndex) => {
const { chooseOptions } = this.state;
this.handleLogger("delete_option", "删除选项");
if (chooseOptions.length < 3) {
message.warning("至少保留2个选项");
} else {
chooseOptions.splice(optionIndex, 1);
this._onSetState();
}
};
/**
* 移动选项
*
* @memberof QuestionInputItem
*/
handleMoveOption = (optionIndex, moveLength) => {
const { chooseOptions } = this.state;
const optionItem = chooseOptions.splice(optionIndex + moveLength, 1);
this.handleLogger("sort_option", "选项排序");
chooseOptions.splice(optionIndex, 0, optionItem[0]);
this._onSetState();
};
/**
* 选择上传文件类型
*
......@@ -205,7 +249,7 @@ class NewQuestionTab extends Component {
handleChangeMedia = (key, uploadItemTarget, inputType) => {
const { mediaFirstType } = this.state;
const mediaArr = _.filter(uploadItemTarget, (mediaItem) => {
return mediaItem.type !== "TEXT";
return mediaItem.type !== "RICH_TEXT";
});
if (mediaArr.length === 0) {
this.setState({ mediaFirstType: key });
......@@ -277,14 +321,14 @@ class NewQuestionTab extends Component {
async uploadFile(event) {
const { fileType, uploadItemTarget } = this.state;
const imageFile = event.target.files[0];
if (!imageFile) return;
const mediaFile = event.target.files[0];
if (!mediaFile) return;
if (fileType === "VOICE") {
if (!QUESTION_FILE_ACCEPT.VOICE.split(",").includes(imageFile.type)) {
if (!QUESTION_FILE_ACCEPT.VOICE.split(",").includes(mediaFile.type)) {
message.warning("文件格式不正确");
return;
}
if (imageFile.size > 30 * 1024 * 1024) {
if (mediaFile.size > 30 * 1024 * 1024) {
Modal.warning({
title: "音频过大",
content: "音频大小超过30M,请压缩后上传",
......@@ -294,11 +338,11 @@ class NewQuestionTab extends Component {
}
if (fileType === "PICTURE") {
if (!QUESTION_FILE_ACCEPT.PICTURE.split(",").includes(imageFile.type)) {
if (!QUESTION_FILE_ACCEPT.PICTURE.split(",").includes(mediaFile.type)) {
message.warning("文件格式不正确");
return;
}
if (imageFile.size > 8 * 1024 * 1024) {
if (mediaFile.size > 8 * 1024 * 1024) {
Modal.warning({
title: "图片过大",
content: "图片大小超过8M,请压缩后上传",
......@@ -308,11 +352,11 @@ class NewQuestionTab extends Component {
}
if (fileType === "VIDEO") {
if (!QUESTION_FILE_ACCEPT.VIDEO.split(",").includes(imageFile.type)) {
if (!QUESTION_FILE_ACCEPT.VIDEO.split(",").includes(mediaFile.type)) {
message.warning("文件格式不正确");
return;
}
if (imageFile.size > 1024 * 1024 * 1024) {
if (mediaFile.size > 1024 * 1024 * 1024) {
Modal.warning({
title: "视频过大",
content: "视频大小超过1G,请压缩后上传",
......@@ -320,21 +364,20 @@ class NewQuestionTab extends Component {
return;
}
}
const originArr = imageFile.name.split(".");
const originArr = mediaFile.name.split(".");
const originType = originArr[originArr.length - 1];
const uploadObj = {
contentType: fileType,
contentType: "QUESTION_STEM",
type: fileType,
contentName: `${window.random_string(16)}.${originType}`,
status: "init",
key: window.random_string(16),
imageFile,
contentName: `${window.random_string(16)}.${originType}`, // 文件名
fileType: originType, // 文件后缀
mediaFile, // url
};
if (["VIDEO", "VOICE"].includes(fileType)) {
try {
await new Promise((resolve) => {
const fileurl = URL.createObjectURL(imageFile);
const fileurl = URL.createObjectURL(mediaFile);
const audioElement = new Audio(fileurl);
audioElement.addEventListener("loadedmetadata", (_event) => {
const duration = audioElement.duration;
......@@ -345,6 +388,8 @@ class NewQuestionTab extends Component {
} catch (error) {
console.log(error);
}
} else if (fileType === "PICTURE") {
uploadObj.size = mediaFile.size;
}
uploadItemTarget.push(uploadObj);
this.setState({}, () => {
......@@ -405,15 +450,19 @@ class NewQuestionTab extends Component {
const _blanksList = this.state.blanksList;
_blanksList.forEach((item) => {
if (item.id === optionItem.id) {
item.answerTagList.map()
item.answerTagList.map();
}
});
this.setState({ blanksList: _blanksList });
}
renderCompletionAnswer = (optionItem) => {
};
renderCompletionAnswer = (optionItem, optionIndex) => {
return (
<div className="completion-answer-box">
<span className="completion-answer-label">{optionItem.innerHTML}.</span>
<span className="completion-answer-label">
{optionItem.innerHTML}
{optionIndex + 1}.
</span>
<div className="completion-answer-content">
{optionItem.answerTagList.map((tag, index) => {
return optionItem.editInput ? (
......@@ -488,9 +537,9 @@ class NewQuestionTab extends Component {
validateStatus
) => {
const isCompletion = this.props.questionTypeKey === "COMPLETION";
const textContent = _.find(
const editorContent = _.find(
contentList,
(contentItem) => contentItem.contentType === "TEXT"
(contentItem) => contentItem.type === "RICH_TEXT"
);
return (
<React.Fragment>
......@@ -511,15 +560,15 @@ class NewQuestionTab extends Component {
markKey={this.markKey}
placehold={placehold}
validateStatus={validateStatus}
detail={contentList}
detailInfo={contentList}
mediaBtn={mediaBtn}
bindChangeContent={(cb, textElemId) => {
this.setState({ textElemId });
textContent.handleChangeContent = cb;
editorContent.handleChangeContent = cb;
}}
onChange={(content, textLength) => {
textContent.content = content;
textContent.textLength = textLength;
editorContent.content = content;
editorContent.textLength = textLength;
this._onSetState();
}}
onUploadMedia={(key) => {
......@@ -529,7 +578,7 @@ class NewQuestionTab extends Component {
)}
</div>
{_.map(contentList, (contentItem, index) => {
const { contentType, content, status } = contentItem;
const { type, content, status } = contentItem;
let dom = "";
if (["init", "fail"].includes(status)) {
return (
......@@ -543,7 +592,7 @@ class NewQuestionTab extends Component {
</div>
);
}
switch (contentType) {
switch (type) {
case "PICTURE":
dom = (
<div className="picture-box">
......@@ -593,7 +642,7 @@ class NewQuestionTab extends Component {
<div
className="question-item_question-content"
style={{
display: ["PICTURE", "VIDEO"].includes(contentType)
display: ["PICTURE", "VIDEO"].includes(type)
? "inline-flex"
: "flex",
}}
......@@ -602,7 +651,7 @@ class NewQuestionTab extends Component {
{dom}
<span
className={
["PICTURE", "VIDEO"].includes(contentType)
["PICTURE", "VIDEO"].includes(type)
? "icon_arrow iconfont"
: "icon_sider iconfont"
}
......@@ -620,40 +669,6 @@ class NewQuestionTab extends Component {
);
};
handleLogger = (en, cn) => {
const { onLogger } = this.props;
onLogger && onLogger(en, cn);
};
/**
* 删除选项
*
* @memberof QuestionInputItem
*/
handleDelOption = (optionIndex) => {
const { chooseOptions } = this.state;
this.handleLogger("delete_option", "删除选项");
if (chooseOptions.length < 3) {
message.warning("至少保留2个选项");
} else {
chooseOptions.splice(optionIndex, 1);
this.setState({ chooseOptions });
}
};
/**
* 移动选项
*
* @memberof QuestionInputItem
*/
handleMoveOption = (optionIndex, moveLength) => {
const { chooseOptions } = this.state;
const optionItem = chooseOptions.splice(optionIndex + moveLength, 1);
this.handleLogger("sort_option", "选项排序");
chooseOptions.splice(optionIndex, 0, optionItem[0]);
this.setState({ chooseOptions });
};
/**
* 重新上传
*
......@@ -662,7 +677,7 @@ class NewQuestionTab extends Component {
handleReupload = (uploadItem) => {
uploadItem.status = "init";
Upload.uploadToOSSEvent(
uploadItem.imageFile,
uploadItem.mediaFile,
uploadItem.contentName,
(url, xhr) => {
uploadItem.content = url;
......@@ -677,7 +692,7 @@ class NewQuestionTab extends Component {
uploadItem.status = "success";
delete uploadItem.xhr;
delete uploadItem.progress;
delete uploadItem.imageFile;
delete uploadItem.mediaFile;
this._onSetState();
},
() => {
......@@ -727,48 +742,12 @@ class NewQuestionTab extends Component {
this.setState({ showRecord: false });
};
/**
* 插入占位符
*
* @memberof QuestionInputItem
*/
insertHTML = () => {
document.getElementById("completionStem").focus();
var blanks = document.createElement("span");
blanks.className = "fill-line";
blanks.innerHTML = `&nbsp;&nbsp;填空${1}&nbsp;&nbsp;`;
blanks.contentEditable = false;
var sel, range;
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
var el = document.createElement("div");
el.appendChild(blanks);
var frag = document.createDocumentFragment(),
node,
lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
};
render() {
const {
stemContent,
chooseOptions,
judgeOptions,
analysisContent,
questionAnswerDesc,
accept,
showRecord,
showScanFile,
......@@ -832,7 +811,7 @@ class NewQuestionTab extends Component {
<span className="answer-tip">请在题干中插入答案占位符</span>
) : (
_.map(blanksList, (item, index) => {
return this.renderCompletionAnswer(item);
return this.renderCompletionAnswer(item, index);
})
)}
</Form.Item>
......@@ -862,12 +841,12 @@ class NewQuestionTab extends Component {
>
{/* 判断 */}
<Radio
checked={optionItem.ifCorrectAnswerItem}
checked={optionItem.isCorrectAnswer === 1}
onClick={() => {
_.each(judgeOptions, (item) => {
item.ifCorrectAnswerItem = false;
item.isCorrectAnswer = 0;
});
optionItem.ifCorrectAnswerItem = true;
optionItem.isCorrectAnswer = 1;
this._onSetState();
}}
/>
......@@ -883,12 +862,16 @@ class NewQuestionTab extends Component {
);
})
: _.map(chooseOptions, (optionItem, optionIndex) => {
const { itemContentVOList, questionItemVO } = optionItem;
const {
questionOptionContentList,
isCorrectAnswer,
} = optionItem;
optionItem.optionSort = optionIndex;
const mediaBtn = ["VOICE", "RECORD", "PICTURE"];
const placeHold =
"必填(1000字以内,可粘贴小图;可以不输入文字,只添加音频或图片)";
return (
<div className="question-item_options__content">
<div className="question-item_options__content" key={optionIndex}>
<div className="question-item_options__setting">
<Form.Item
validateStatus={radioValidate}
......@@ -899,27 +882,28 @@ class NewQuestionTab extends Component {
}
>
{/* 单选 */}
{this.props.questionTypeKey === "THE_RADIO" && (
{this.props.questionTypeKey ===
"SINGLE_CHOICE" && (
<Radio
checked={questionItemVO.ifCorrectAnswerItem}
checked={isCorrectAnswer}
onClick={() => {
_.each(chooseOptions, (o) => {
o.questionItemVO.ifCorrectAnswerItem = false;
o.isCorrectAnswer = 0;
});
questionItemVO.ifCorrectAnswerItem = true;
optionItem.isCorrectAnswer = 1;
this._onSetState();
}}
/>
)}
{/* 多选 or 不定项 */}
{["INDEFINITE_SELECT", "MULTI_SELECT"].includes(
{["INDEFINITE_SELECT", "MULTI_CHOICE"].includes(
this.props.questionTypeKey
) && (
<Checkbox
checked={questionItemVO.ifCorrectAnswerItem}
checked={isCorrectAnswer === 1}
onChange={(e) => {
const checked = e.target.checked;
questionItemVO.ifCorrectAnswerItem = checked;
const checked = e.target.checked ? 1 : 0;
optionItem.isCorrectAnswer = checked;
this._onSetState();
}}
/>
......@@ -937,7 +921,7 @@ class NewQuestionTab extends Component {
help={this.state[`optionsText_${optionIndex}`]}
>
{this.renderContent(
itemContentVOList,
questionOptionContentList,
placeHold,
mediaBtn,
"options",
......@@ -1000,7 +984,7 @@ class NewQuestionTab extends Component {
<Form.Item name="analysis" label="答案解析">
<div className="question-item_analysis__content">
{this.renderContent(
analysisContent,
questionAnswerDesc,
"1000字以内,可粘贴小图",
["VOICE", "RECORD", "PICTURE", "VIDEO"],
"analysis"
......
......@@ -2,13 +2,15 @@
* @Author: yuananting
* @Date: 2021-02-22 10:59:43
* @LastEditors: yuananting
* @LastEditTime: 2021-03-05 09:39:47
* @LastEditTime: 2021-03-13 20:29:29
* @Description: 助学工具-题库-题库主页面侧边栏
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Input, Button, Tree } from "antd";
import "./QuestionBankSider.less";
import User from "@/common/js/user";
import QuestionBankService from "@/domains/question-bank-domain/QuestionBankService";
const { Search } = Input;
const { DirectoryTree } = Tree;
......@@ -17,140 +19,59 @@ class QuestionBankSider extends Component {
constructor(props) {
super(props);
this.state = {
selectedKeys: ["0"],
searchValue: null,
NewEditQuestionBankCategory: null, //新增或编辑分类模态框
ImportCourseCategory: null, // 引用课程分类模态框
treeData: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023655839768500",
parentId: "0",
rootId: "1367023655839768500",
categoryLevel: 0,
categoryName: "未分类",
sort: 0,
},
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023655839768577",
parentId: "0",
rootId: "1367023655839768577",
categoryLevel: 0,
categoryName: "分类1",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023709942095873",
parentId: "1367023655839768577",
rootId: "1367023655839768577",
categoryLevel: 1,
categoryName: "test",
sort: 0,
},
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023725184196609",
parentId: "1367023655839768577",
rootId: "1367023655839768577",
categoryLevel: 1,
categoryName: "分类2",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367023805563838466",
parentId: "1367023725184196609",
rootId: "1367023655839768577",
categoryLevel: 2,
categoryName: "test",
sort: 0,
},
],
sort: 1,
},
],
sort: 1,
},
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367049864988516353",
parentId: "0",
rootId: "1367049864988516353",
categoryLevel: 0,
categoryName: "分类2",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367049931656978433",
parentId: "1367049864988516353",
rootId: "1367049864988516353",
categoryLevel: 1,
categoryName: "分类2",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367049973746819074",
parentId: "1367049931656978433",
rootId: "1367049864988516353",
categoryLevel: 2,
categoryName: "分类3",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367050021431861249",
parentId: "1367049973746819074",
rootId: "1367049864988516353",
categoryLevel: 3,
categoryName: "分类4",
subList: [
{
tenantId: "1122334455667788",
userId: "12345678",
source: 0,
id: "1367050063035162626",
parentId: "1367050021431861249",
rootId: "1367049864988516353",
categoryLevel: 4,
categoryName: "分类5",
sort: 0,
},
],
sort: 0,
},
],
sort: 0,
},
],
sort: 0,
},
],
sort: 2,
},
],
treeData: this.props.treeData || [],
};
}
componentDidMount() {
this.setState({ treeData: this.renderTreeNodes(this.state.treeData) });
this.queryCategoryTree();
}
/** 获取树状第一级key 设置默认展开第一项 */
getFirstLevelKeys = (data) => {
let firstLevelKeys = [];
data.forEach((item) => {
if (item.categoryLevel === 0) {
firstLevelKeys.push(item.key);
}
});
return firstLevelKeys;
};
/** 树状展开事件 */
onExpand = (expandedKeys) => {
this.setState({ expandedKeys });
};
/** 树状选中事件 */
onSelect = (selectedKeys) => {
this.setState({ selectedKeys });
this.props.getSelectedCategoryId(selectedKeys)
};
// 查询分类树
queryCategoryTree = (categoryName) => {
let query = {
source: 0,
categoryName,
userId: User.getUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryQuestionCategoryTree(query).then((res) => {
const { categoryList = [] } = res.result;
const defaultNode = { id: "0", categoryName:"未分类", categoryCount: 0}
categoryList.unshift(defaultNode);
this.setState({ treeData: this.renderTreeNodes(categoryList, categoryName) });
this.setState({
expandedKeys: this.getFirstLevelKeys(categoryList),
});
});
};
renderTreeNodes = (data, value) => {
return data.map((item) => {
item.title = item.categoryName;
......@@ -166,7 +87,7 @@ class QuestionBankSider extends Component {
</span>
);
item.icon =
item.id === "default" ? (
item.categoryName === "未分类" ? (
<span className="icon iconfont" style={{ color: "#FBD140" }}>
&#xe7f6;
</span>
......@@ -175,15 +96,15 @@ class QuestionBankSider extends Component {
&#xe7f1;
</span>
);
if (item.subList) {
item.children = this.renderTreeNodes(item.subList, value);
if (item.sonCategoryList) {
item.children = this.renderTreeNodes(item.sonCategoryList, value);
}
return item;
});
};
render() {
const { treeData } = this.state;
const { treeData, expandedKeys, selectedKeys } = this.state;
return (
<div className="question-bank-sider">
<div className="sider-title">题目分类</div>
......@@ -192,9 +113,7 @@ class QuestionBankSider extends Component {
placeholder="搜索名称分类"
onSearch={(value) => {
// TODO 调用查询分类接口
this.setState({
treeData: this.renderTreeNodes(treeData, value),
});
this.queryCategoryTree(value);
}}
/>
<div className="sider-btn">
......@@ -208,17 +127,16 @@ class QuestionBankSider extends Component {
分类管理
</Button>
</div>
<DirectoryTree
defaultSelectedKeys={["1367023655839768500"]}
showIcon
treeData={treeData}
/>
{treeData.length === 1 && (
<div className="empty-tree-tip">
还没有分类,需<span className="empty-tree-btn">引用课程分类</span>
哦~
</div>
)}
<div className="sider-tree">
<DirectoryTree
expandedKeys={expandedKeys}
onExpand={this.onExpand}
selectedKeys={selectedKeys}
onSelect={this.onSelect}
showIcon
treeData={treeData}
/>
</div>
{this.state.NewEditQuestionBankCategory}
{this.state.ImportCourseCategory}
</div>
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-22 12:02:34
* @LastEditors: yuananting
* @LastEditTime: 2021-03-04 16:59:07
* @LastEditTime: 2021-03-11 20:29:44
* @Description: 助学工具-题库-题库主页面侧边栏样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -22,34 +22,38 @@
.sider-btn {
margin-bottom: 16px;
}
.empty-tree-tip {
text-align: center;
margin-top: 100%;
.empty-tree-btn {
color: #ffb714;
cursor: pointer;
.sider-tree {
width: 266px;
overflow: scroll;
height: calc(100vh - 300px);
.empty-tree-tip {
text-align: center;
margin-top: 100%;
.empty-tree-btn {
color: #ffb714;
cursor: pointer;
}
}
}
.ant-tree.ant-tree-directory {
font-size: 14px;
font-weight: 400;
color: #666666;
.anticon {
.ant-tree.ant-tree-directory {
font-size: 14px;
font-weight: 400;
color: #666666;
}
.ant-tree-treenode {
height: 44px;
padding: 0;
span {
line-height: 44px;
}
.ant-tree-node-content-wrapper.ant-tree-node-selected {
width: 260px;
.anticon {
color: #666666;
}
.ant-tree-treenode {
height: 44px;
padding: 0;
span {
line-height: 44px;
white-space: nowrap;
}
.ant-tree-node-content-wrapper.ant-tree-node-selected {
color: #666666;
}
}
}
// .ant-tree-treenode-selected:hover::before,
// .ant-tree-treenode-selected::before {
// background: rgb(255 251 240);
// }
}
}
......@@ -73,7 +73,7 @@ class QuestionEditor extends Component {
renderEditor() {
const { editorId } = this.state;
const { detail = {}, onChange, bindChangeContent } = this.props;
const { detail = {}, detailInfo = {}, onChange, bindChangeContent } = this.props;
const editorRoot = new E(
`#editor${editorId}_tabbar`,
`#editor${editorId}_content`
......@@ -103,15 +103,15 @@ class QuestionEditor extends Component {
str = str.replace(/<\/?[^>]*>/g, "");
str = str.replace(/[ | ]*\n/g, "\n");
str = str.replace(/\&nbsp\;/gi, " ");
str = str.replace(/[\r\n]/g,"");
if (str.length > 1000) {
str = str.substring(0, 1000);
message.error("内容过长,不能超过1000字");
}
return str;
return str
};
editorRoot.customConfig.onchange = (html) => {
console.log(html)
const { focusFlag } = this.state;
const textLength = editorRoot.txt.text().replace(/\&nbsp\;/gi, " ")
.length;
......@@ -119,15 +119,14 @@ class QuestionEditor extends Component {
? html.match(/<img/g).length * 2
: 0;
const contentLength = imgLength + textLength;
const pHeight = document.getElementById(`editor${editorId}_content`)
.firstChild.firstChild.offsetHeight;
if (pHeight > 30 || imgLength > 0) {
const divHeight = document.getElementById(`editor${editorId}_content`)
.firstChild.offsetHeight;
if (divHeight > 30 || imgLength > 0) {
this.setState({ isShowSingleInput: false });
} else {
this.setState({ isShowSingleInput: true });
}
this.setState({ errorInput: contentLength > 1000 });
this.setState(
{ contentLength, visiblePlacehold: contentLength === 0 && !focusFlag },
() => {
......@@ -152,16 +151,16 @@ class QuestionEditor extends Component {
visibleMediaBox: true,
visiblePlacehold: false,
});
bindChangeContent && bindChangeContent(this.handleChangeContent, editorRoot.textElemId)
};
editorRoot.create();
this.editorRoot = editorRoot;
// 初始化设置内容
if (detail.content) {
const contentHtml = /^\<p/.test(detail.content)
? detail.content
: `<p>${detail.content}</p>`;
editorRoot.txt.html(contentHtml);
editorRoot.txt.html(detail.content);
const textLength = editorRoot.txt.text().replace(/\&nbsp\;/gi, " ")
.length;
const imgLength = contentHtml.match(/<img/g)
......@@ -178,7 +177,28 @@ class QuestionEditor extends Component {
}
);
}
bindChangeContent && bindChangeContent(this.handleChangeContent, editorRoot.textElemId);
if (detailInfo.content) {
const contentHtml = /^\<p/.test(detailInfo.content)
? detailInfo.content
: `<p>${detailInfo.content}</p>`;
editorRoot.txt.html(detailInfo.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);
}
);
}
bindChangeContent && bindChangeContent(this.handleChangeContent);
}
render() {
......@@ -219,12 +239,13 @@ class QuestionEditor extends Component {
});
}}
>
<div
className="editor-box"
id={`editor${editorId}_tabbar`}
style={{ display: "none" }}
></div>
<div
<div
className={
isShowSingleInput ? "editor-box-single " : "editor-box-multiple"
}
......@@ -287,4 +308,5 @@ class QuestionEditor extends Component {
);
}
}
export default QuestionEditor;
/*
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-03-13 21:04:36
* @Description: 助学工具-题库-题目管理主页面列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import {
Table,
Switch,
ConfigProvider,
Empty,
Row,
Input,
Select,
Tooltip,
Space,
Button,
Modal,
} 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";
const { Search } = Input;
const questionTypeList = [
{
label: "单选题",
value: "SINGLE_CHOICE",
},
{
label: "多选题",
value: "MULTI_CHOICE",
},
{
label: "判断题",
value: "JUDGE",
},
{
label: "填空题",
value: "GAP_FILLING",
},
{
label: "不定项选择题",
value: "INDEFINITE_CHOICE",
},
];
class QuestionManageContent extends Component {
constructor(props) {
super(props);
this.state = {
query: {
current: 1,
size: 10,
order: "ACCURACY_DESC", // 排序规则[ ACCURACY_DESC, ACCURACY_ASC, CREATED_DESC, CREATED_ASC, UPDATED_DESC, UPDATED_ASC ]
categoryId: null, // 当前题库分类Id
questionName: null, // 题目名称
questionType: null, // 题目类型
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
},
questionTypeList: [], // 题型列表
dataSource: [],
totalCount: 0,
};
}
componentDidMount() {
this.queryQuestionPageList();
}
shouldComponentUpdate(nextProps, nextState) {
const { selectedCategoryId } = nextProps;
const _query = this.state.query;
if (this.props.selectedCategoryId !== selectedCategoryId) {
_query.categoryId = selectedCategoryId;
this.setState({ query: _query }, () => this.queryQuestionPageList());
}
return true;
}
queryQuestionPageList = () => {
QuestionBankService.queryQuestionPageList(this.state.query).then((res) => {
const { records = [], total = 0 } = res.result;
this.setState({ dataSource: records });
});
};
handleCreateQuestionBank = () => {
window.RCHistory.push({
pathname: `/create-new-question?categoryId=${this.state.query.categoryId}`,
});
};
delCategoryConfirm(record) {
return Modal.confirm({
title: "提示",
content: "确定要删除此题目吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
cancelText: "取消",
onOk: () => {
this.deleteQuestion(record);
},
});
}
deleteQuestion = (record) => {
let params = {
id: record.id,
categoryId: record.categoryId,
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
}
console.log(params)
QuestionBankService.delCategory(params).then(res=>{
console.log(res)
})
}
// 表头设置
parseColumns = () => {
const columns = [
{
title: "题目",
key: "questionStem",
dataIndex: "questionStem",
width: 300,
render: (val, record) => {
return <div className="record-name">{val}</div>;
},
},
{
title: "题型",
key: "questionTypeEnum",
dataIndex: "questionTypeEnum",
},
{
title: "正确率",
key: "accuracy",
dataIndex: "accuracy",
sorter: true,
},
{
title: "更新时间",
key: "updateTime",
dataIndex: "updateTime",
sorter: true,
render: (val) => {
return formatDate("YYYY-MM-DD H:i:s", val);
},
},
{
title: "操作",
key: "operate",
dataIndex: "operate",
fixed: "right",
render: (val, record) => {
return (
<div className="record-operate">
<div
className="record-operate__item"
onClick={() => console.log("预览")}
>
预览
</div>
<span className="record-operate__item split"> | </span>
<div
className="record-operate__item"
onClick={() => console.log("预览")}
>
编辑
</div>
<span className="record-operate__item split"> | </span>
<div
className="record-operate__item"
onClick={() => this.delCategoryConfirm(record)}
>
删除
</div>
</div>
);
},
},
];
return columns;
};
// 自定义表格空状态
customizeRenderEmpty = () => {
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={
<div>
还没有题目,快去
<span
className="empty-list-tip"
onClick={() => {
window.RCHistory.push({
pathname: "/create-question-bank",
});
}}
>
新建一个
</span>
吧!
</div>
}
></Empty>
);
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
let _query = this.props.query;
_query.size = size;
this.props.onChange(_query);
};
// 改变搜索条件
handleChangeQuery = (searchType, value) => {
this.setState(
{
query: {
...this.state.query,
[searchType]: value || null,
current: 1,
},
},
() => {
if (searchType === "questionName") return;
this.queryQuestionPageList();
}
);
};
render() {
const { dataSource = [], totalCount, query } = this.state;
const { current, size, categoryId } = query;
return (
<div className="question-manage-content">
<div className="question-manage-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-label">题目:</span>
<Search
placeholder="搜索题目名称"
style={{ width: "calc(100% - 84px)" }}
onChange={(e) => {
this.handleChangeQuery("questionName", e.target.value);
}}
onSearch={() => {
this.queryQuestionPageList();
}}
/>
</div>
<div className="search-condition__item">
<span className="search-label">题型:</span>
<Select
placeholder="请选择题目类型"
style={{ width: "calc(100% - 70px)" }}
showSearch
allowClear
filterOption={(inputVal, option) =>
option.props.children.includes(inputVal)
}
onChange={(value) => {
if(_.isEmpty(value)) {
this.handleChangeQuery("questionType", value);
}
}}
onSelect={(value) => {
this.handleChangeQuery("questionType", value);
}}
>
{_.map(questionTypeList, (item, index) => {
return (
<Select.Option value={item.value} key={item.key}>
{item.label}
</Select.Option>
);
})}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={this.handleReset}
>
&#xe61b;{" "}
</span>
</Tooltip>
</div>
</Row>
</div>
{!["0", null].includes(categoryId) && (
<Space size="large">
<Button type="primary" onClick={this.handleCreateQuestionBank}>
新建题目
</Button>
<Button
onClick={() => {
console.log("批量导入");
}}
>
批量导入
</Button>
</Space>
)}
<div className="question-manage-list">
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
<Table
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
onChange={this.handleChangeTable}
pagination={false}
bordered
/>
</ConfigProvider>
{totalCount > 0 && (
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.props.onChange(_query);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
)}
</div>
</div>
);
}
}
export default QuestionManageContent;
/*
* @Author: yuananting
* @Date: 2021-02-25 10:42:51
* @Date: 2021-02-25 11:26:28
* @LastEditors: yuananting
* @LastEditTime: 2021-02-25 11:11:10
* @Description: 助学工具-题库-题目管理主页面头部搜索样式
* @LastEditTime: 2021-03-13 16:42:41
* @Description: 助学工具-题库-题目管理右侧内容样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-manage-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
.question-manage-content {
.question-manage-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
.search-label {
vertical-align: middle;
display: inline-block;
height: 32px;
line-height: 32px;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
.search-label {
vertical-align: middle;
display: inline-block;
height: 32px;
line-height: 32px;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left: 4px;
}
}
}
}
.data-icon {
cursor: pointer;
}
.reset-fold-area {
position: absolute;
right: 12px;
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
.question-manage-list {
position: relative;
margin-top: 16px;
.empty-list-tip {
color: #ffb714;
cursor: pointer;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left: 4px;
.record-name {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
}
.record-operate {
display: flex;
&__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
}
}
}
}
.data-icon {
cursor: pointer;
}
/*
* @Author: yuananting
* @Date: 2021-02-25 10:30:52
* @LastEditors: yuananting
* @LastEditTime: 2021-02-25 10:44:46
* @Description: 助学工具-题库-题目管理主页面头部搜索
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import "./QuestionManageFilter.less";
import { Row, Input, Select, Tooltip } from "antd";
const { Search } = Input;
const DEFAULT_QUERY = {
questionName: null, // 题目名称
questionType: null, // 题型
};
class QuestionManageFilter extends Component {
constructor(props) {
super(props);
this.state = {
query: { ...DEFAULT_QUERY }, // 查询条件
questionTypeList: [], // 题型列表
};
}
render() {
const {
query: { questionName, questionType },
questionTypeList,
} = this.state;
return (
<div className="question-manage-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-label">题目:</span>
<Search
value={questionName}
placeholder="搜索题目名称"
style={{ width: "calc(100% - 84px)" }}
onChange={(value) => {
console.log(value);
}}
/>
</div>
<div className="search-condition__item">
<span className="search-label">题型:</span>
<Select
value={questionType}
placeholder="请选择题目类型"
style={{ width: "calc(100% - 70px)" }}
showSearch
allowClear
filterOption={(input, option) => option}
onChange={(value) => {
console.log(value);
}}
>
{_.map(questionTypeList, (item, index) => {
return (
<Select.Option value={item.id} key={item.id}>
{item}
</Select.Option>
);
})}
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选">
<span
className="resetBtn iconfont icon"
onClick={this.handleReset}
>
&#xe61b;{" "}
</span>
</Tooltip>
</div>
</Row>
</div>
);
}
}
export default QuestionManageFilter;
/*
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-03-04 16:55:55
* @Description: 助学工具-题库-题目管理主页面列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Table, Switch, ConfigProvider, Empty } from "antd";
import { PageControl } from "@/components";
import "./QuestionManageList.less";
class QuestionManageList extends Component {
// 表头设置
parseColumns = () => {
const columns = [
{
title: "题目",
key: "questionName",
dataIndex: "questionName",
width: 300,
render: (val, record) => {
return <div className="record-name">{val}</div>;
},
},
{
title: "题型",
key: "questionType",
dataIndex: "questionType",
},
{
title: "正确率",
key: "accuracy",
dataIndex: "accuracy",
sorter: true,
},
{
title: "更新时间",
key: "updated",
dataIndex: "updated",
sorter: true,
render: (val) => {
return formatDate("YYYY-MM-DD H:i:s", val);
},
},
{
title: "操作",
key: "operate",
dataIndex: "operate",
fixed: "right",
render: (val, record) => {
return (
<div className="record-operate">
<div
className="operate__item"
onClick={() => console.log("预览")}
>
预览
</div>
<span className="operate__item split"> | </span>
<div
className="operate__item"
onClick={() => console.log("编辑")}
>
编辑
</div>
<span className="operate__item split"> | </span>
<div
className="operate__item"
onClick={() => console.log("删除")}
>
删除
</div>
</div>
);
},
},
];
return columns;
};
// 自定义表格空状态
customizeRenderEmpty = () => {
return (
<Empty
image="https://image.xiaomaiketang.com/xm/emptyTable.png"
imageStyle={{
height: 100,
}}
description={
<div>
还没有题目,快去
<span
className="empty-list-tip"
onClick={() => {
window.RCHistory.push({
pathname: "/create-question-bank",
});
}}
>
新建一个
</span>
吧!
</div>
}
></Empty>
);
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
let _query = this.props.query;
_query.size = size;
this.props.onChange(_query);
};
render() {
const { dataSource = [], totalCount, query } = this.props;
const { current, size } = query;
return (
<div className="question-manage-list">
<ConfigProvider renderEmpty={this.customizeRenderEmpty}>
<Table
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
onChange={this.handleChangeTable}
pagination={false}
bordered
/>
</ConfigProvider>
{totalCount > 0 && (
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.props.onChange(_query);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
)}
</div>
);
}
}
export default QuestionManageList;
/*
* @Author: yuananting
* @Date: 2021-02-25 11:26:28
* @LastEditors: yuananting
* @LastEditTime: 2021-02-25 11:39:08
* @Description: 助学工具-题库-题目管理主页面列表数据样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.question-manage-list {
position: relative;
margin-top: 16px;
.empty-list-tip {
color: #ffb714;
cursor: pointer;
}
.record-name {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
}
.record_operate {
display: flex;
&__item {
color: #5289fa;
cursor: pointer;
&.split {
margin: 0 8px;
color: #bfbfbf;
}
}
}
}
\ No newline at end of file
/*
* @Author: yuananting
* @Date: 2021-02-25 11:01:06
* @LastEditors: yuananting
* @LastEditTime: 2021-03-04 17:31:45
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Space, Button } from "antd";
class QuestionManageOpt extends Component {
handleCreateQuestionBank = () => {
window.RCHistory.push({
pathname: "/create-new-question",
});
};
render() {
return (
<Space size="large">
<Button type="primary" onClick={this.handleCreateQuestionBank}>
新建题目
</Button>
<Button
onClick={() => {
console.log("批量导入");
}}
>
批量导入
</Button>
</Space>
);
}
}
export default QuestionManageOpt;
......@@ -226,11 +226,11 @@ const XMAudio = (props) => {
<div className="process-area">
<div
className="complete-area"
style={{ width: `${(playedTime / totalTime) * 180}px ` }}
style={{ width: `${(playedTime / totalTime) * 150}px ` }}
/>
<div
className="flag"
style={{ left: `${(playedTime / totalTime) * 180}px ` }}
style={{ left: `${(playedTime / totalTime) * 150}px ` }}
onMouseDown={(e) => putDownFlag(e)}
onMouseOver={(e)=> mouseOver(e)}
onMouseLeave={(e)=>mouseLeave(e)}
......
/*
* @Author: chenjianyu
* @Date: 2020-09-12 17:00:44
* @LastEditTime: 2021-03-01 17:25:37
* @LastEditTime: 2021-03-13 11:19:38
* @LastEditors: yuananting
* @Description: 答题模式模板
* @Copyright © 2020 杭州杰竞科技有限公司 版权所有
......@@ -44,6 +44,46 @@ export function defineQuestionData(questionType) {
}
}
export function defineQuestionInfo(questionType) {
return {
questionTypeEnum: questionType, // 题目类型
questionStemList:[
{
contentType: "QUESTION_STEM", // 内容类型(默认题干)
content: "", // 内容
type: "RICH_TEXT", // 内容项形式(0:富文本 1:文字 2:图片 3:语音 4:视频 5文件 6.课件)
}
], // 题干
optionList: [], // 非填空题选项
gapFillingAnswerList: [
{
correctAnswerList: []
}
], //填空题填空项
questionAnswerDescList: [
{
contentType: "QUESTION_ANSWER_DESC", // 内容类型(默认解析)
content: "", // 内容
type: "RICH_TEXT", // 内容项形式(0:富文本 1:文字 2:图片 3:语音 4:视频 5文件 6.课件)
}
], //答案解析
showBox: true, // 显示录音弹窗
}
}
export function defineOptionInfo() {
return {
isCorrectAnswer: false, // 是否为正确答案选项
questionOptionContentList: [ // 选项内容
{
contentType: "QUESTION_OPTION", // 内容类型(默认选项)
content: "", // 内容
type: "RICH_TEXT", // 内容项形式(0:富文本 1:文字 2:图片 3:语音 4:视频 5文件 6.课件)
}
]
}
}
export function defineOptionData(content = '') {
return {
itemContentVOList: [{
......
/*
* @Author: yuananting
* @Date: 2021-02-23 11:43:43
* @LastEditors: yuananting
* @LastEditTime: 2021-02-25 10:32:53
* @Description: 助学工具-题库-题库引用课程分类模态框
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from "react";
import { Modal, Tree, Card } from "antd";
import { PageControl } from "@/components";
import "./ImportCourseCategory.less";
class ImportCourseCategory extends Component {
constructor(props) {
super(props);
this.state = {
query: {
current: 1,
size: 1,
},
totalCount: 0,
};
}
onSelect = (selectedKeys, info) => {
console.log("selected", selectedKeys, info);
};
onCheck = (checkedKeys, info) => {
console.log("onCheck", checkedKeys, info);
};
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
const _query = { ...this.props.query };
_query.size = size;
// 更新请求参数
this.setState({ _query });
// TODO请求
};
confirmImport = () => {
// 判断是否已经选择
// TODO save
this.props.close();
};
render() {
const treeData = [
{
title: "parent 1",
key: "0-0",
children: [
{
title: "parent 1-0",
key: "0-0-0",
disabled: true,
children: [
{
title: "leaf",
key: "0-0-0-0",
disableCheckbox: true,
},
{
title: "leaf",
key: "0-0-0-1",
},
],
},
{
title: "parent 1-1",
key: "0-0-1",
children: [
{
title: <span style={{ color: "#1890ff" }}>sss</span>,
key: "0-0-1-0",
},
],
},
],
},
];
const { query, totalCount } = this.state;
const { current, size } = query;
return (
<Modal
title="引用课程分类"
visible={true}
onOk={this.confirmImport}
onCancel={() => this.props.close()}
>
<div className="import-course-title">请选择需要的分类</div>
<Card size="small" title="分类名称">
<Tree
checkable
blockNode
height={200}
defaultExpandedKeys={["0-0-0", "0-0-1"]}
defaultSelectedKeys={["0-0-0", "0-0-1"]}
defaultCheckedKeys={["0-0-0", "0-0-1"]}
onSelect={this.onSelect}
onCheck={this.onCheck}
treeData={treeData}
/>
</Card>
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.props.onChange(_query);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
</Modal>
);
}
}
export default ImportCourseCategory;
/*
* @Author: yuananting
* @Date: 2021-02-23 14:15:07
* @LastEditors: yuananting
* @LastEditTime: 2021-02-23 14:23:19
* @Description: 助学工具-题库-题库引用课程分类模态框样式
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
.ant-modal .ant-modal-content .ant-modal-body {
padding-top: 16px !important;
}
.import-course-title {
margin-bottom: 16px;
}
......@@ -2,34 +2,89 @@
* @Author: yuananting
* @Date: 2021-02-22 17:51:28
* @LastEditors: yuananting
* @LastEditTime: 2021-03-04 11:47:20
* @LastEditTime: 2021-03-13 14:26:31
* @Description: 助学工具-题库-题库新建或编辑题库分类模态框
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
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) {
super(props);
this.state = {
categoryName: null, // 分类名称
treeData: [],
categoryName:
this.props.type === "edit" ? this.props.node.categoryName : null,
};
}
componentDidMount() {
this.queryCategoryTree();
// this.getSameLevelNodes(this.props.treeData);
}
// 查询分类树
queryCategoryTree = () => {
let query = {
source: 0,
userId: User.getUserId(),
tenantId: User.getStoreId(),
};
QuestionBankService.queryCategoryTree(query).then((res) => {
const { result = [] } = res;
this.setState({ treeData: result });
});
};
// 确定新增或编辑
confirmOperate = async () => {
try {
await this.formRef.current.validateFields();
// TODO save
this.props.close();
} catch (e) {
console.log(e);
const { categoryName } = this.state;
const { node, addLevelType, type } = this.props;
let params = {
source: 0,
tenantId: User.getStoreId(),
userId: User.getUserId(),
};
if (type === "new") {
//新增
params.categoryName = categoryName;
if (addLevelType === "equal") {
params.parentId = node ? node.parentId : 0
params.categoryLevel = node ? node.categoryLevel : 0;
} else {
params.parentId = node.id;
params.categoryLevel = node.categoryLevel + 1;
}
try {
await this.formRef.current.validateFields();
QuestionBankService.addCategory(params).then((res) => {
if (res.success) {
this.props.close();
}
});
} catch (e) {
console.log(e);
}
} else {
// 编辑
params.categoryId = node.id;
params.parentId = node.parentId;
params.categoryLevel = node.categoryLevel;
params.categoryName = categoryName;
try {
await this.formRef.current.validateFields();
QuestionBankService.editCategory(params).then((res) => {
if (res.success) {
this.props.close();
}
});
} catch (e) {
console.log(e);
}
}
};
......@@ -40,32 +95,32 @@ class NewEditQuestionBankCategory extends Component {
nodes.push(item);
}
if (item.children) {
nodes.push(...this.getEqualLevelNodes(item.children, parentId))
nodes.push(...this.getEqualLevelNodes(item.children, parentId));
}
})
});
return nodes;
}
};
getChildLevelNodes = (data, id) => {
let nodes = [];
data.forEach((item) => {
if(item.id === id && item.children) {
if (item.id === id && item.children) {
nodes.push(...item.children);
}
if (item.children) {
nodes.push(...this.getChildLevelNodes(item.children, id))
nodes.push(...this.getChildLevelNodes(item.children, id));
}
})
});
return nodes;
}
};
getSameLevelNodes = (data, type) => {
let sameLevelNodes = [];
const { id, parentId } = this.props;
if (type === "equal") {
let parentId = this.props.node ? this.props.node.parentId : "0";
sameLevelNodes = this.getEqualLevelNodes(data, parentId);
} else {
sameLevelNodes = this.getChildLevelNodes(data, id);
sameLevelNodes = this.getChildLevelNodes(data, this.props.node.id);
}
return sameLevelNodes;
};
......@@ -107,7 +162,10 @@ class NewEditQuestionBankCategory extends Component {
},
({ getFieldValue }) => ({
validator(_, value) {
let sameLevelNodes = _that.getSameLevelNodes(treeData, addLevelType);
let sameLevelNodes = _that.getSameLevelNodes(
treeData,
addLevelType
);
if (_that.checkExist(sameLevelNodes, value)) {
return Promise.reject("此分类名称已存在");
} else {
......@@ -118,7 +176,7 @@ class NewEditQuestionBankCategory extends Component {
]}
>
<Input
value={categoryName}
defaultValue={categoryName}
placeholder={`请输入${title},最多10个字`}
maxLength={10}
onChange={(e) => {
......
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