Merge pull request 'add some components 01' (#6) from binary/OpenPBL:pr02 into master

This commit is contained in:
OpenCT 2022-08-19 20:26:31 +08:00
commit 7786ef3ef1
19 changed files with 2809 additions and 0 deletions

473
web/src/BuildTeam.js Normal file
View File

@ -0,0 +1,473 @@
import React, {Component} from "react";
import {Button, Form, Input, Modal, Select, Space, Table, Tag, message} from "antd";
import {withRouter} from "react-router-dom";
import * as ProjectBackend from "./backend/ProjectBackend";
import ModalCard from "./ModulaCard";
import "./BuildTeam.less";
import {WarningTwoTone} from "@ant-design/icons";
const {Search, TextArea} = Input;
const {confirm} = Modal;
const {Option} = Select;
class index extends Component {
constructor(props) {
super(props);
this.state = {
classes: props,
loadingState: false,
createLoading: false,
roleChangeForm: {
show: false,
assignment_id: "",
new_role: "",
updateLoading: false,
},
addMemberForm: {
show: false,
loadingState: false,
role: 1,
user_id: "",
},
emailForm: {
show: false,
loadingState: false,
message: "",
send_time: "",
sender: "binary",
destination: [],
subject: "",
},
memberList: [],
};
}
columns = [{
title: "姓名",
align: "center",
render: (text, record) => (
<span>{record.info.name}</span>
),
}, {
title: "年龄",
align: "center",
render: (text, record) => (
<span>{record.info.birthday}</span>
),
}, {
title: "性别",
align: "center",
render: (text, record) => (
<span>{record.info.gender ? "男" : "女"}</span>
),
}, {
title: "专业",
align: "center",
render: (text, record) => (
<Tag color="processing">{record.info.owner}</Tag>
),
}, {
title: "职位",
align: "center",
render: (text, record) => (
<span>{record.info.type}</span>
),
}, {
title: "邮箱",
align: "center",
render: (text, record) => (
<Button type="link" onClick={() => {
this.setState({
emailForm: Object.assign(this.state.emailForm, {destination: [record.info.email], send_time: new Date().getTime().toString(), show: true}),
});
}}>
{record.info.email}
</Button>
),
}, {
title: "组织",
align: "center",
render: (text, record) => (
<span>{record.info.signupApplication}</span>
),
}, {
title: "位置",
align: "center",
render: (text, record) => (
<span>{record.info.region}</span>
),
}, {
title: "电话",
align: "center",
render: (text, record) => (
<span>{record.info.phone}</span>
),
}, {
title: "项目角色",
align: "center",
render: (text, record) => {
let roleList = ["管理员", "专家", "学科助理", "教师", "外审专家"];
return (
<Tag color="green">{roleList[record.role - 1]}</Tag>
);
},
}, {
title: "状态",
align: "center",
render: (text, record) => (
<>
{
record.info.isOnline ? (
<Tag color="#87d068">已确认 </Tag>
) : (
<Tag color="#f50">未确认 ×</Tag>
)
}
</>
),
}, {
title: "管理",
align: "center",
render: (text, record) => (
<Space size="middle">
<Button type="link" onClick={() => {
this.setState({
roleChangeForm: Object.assign(this.state.roleChangeForm, {
assignment_id: record.Id,
new_role: record.role,
show: true,
}),
});
}}>更改角色</Button>
<Button type="link" danger onClick={() => {
confirm({
icon: <WarningTwoTone />,
content: "该删除操作不可逆,是否继续?",
okText: "确认移除",
cancelText: "取消移除",
// onOk: () => {
// this.setState({
// loadingState: true
// });
// request({
// url: baseURL + `/review/proj/assign/${record.uuid}`,
// // url:`http://49.232.73.36:8081/review/proj/assign/${record.uuid}`,
// method: "DELETE"
// }).then(res => {
// this.setState({
// loadingState: false
// });
// message.success("删除成功!");
// this.getProjectMember();
// }).catch(err => {
// message.error(err.message || "请求错误!");
// this.setState({
// loadingState: false
// });
// });
// },
onOk: () => { },
onCancel() {
message.info("已取消移除");
},
});
}}>移除人员</Button>
</Space>
),
}]
emailFormRef = React.createRef()
componentDidMount() {
this.getProjectMember();
}
getProjectMember = () => {
this.setState({
loadingState: true,
});
ProjectBackend.GetProjectAssignments(this.props.match.params.project_id.split("_").join("/")).then(res => {
let memberList = [];
memberList = [...res.data.admins.map(item => {
item.roleName = "admin";
return item;
}), ...res.data.assistants.map(item => {
item.roleName = "assistant";
return item;
}), ...res.data.experts.map(item => {
item.roleName = "expert";
return item;
}), ...res.data.out_experts.map(item => {
item.roleName = "out_expert";
return item;
}), ...res.data.teachers.map(item => {
item.roleName = "teacher";
return item;
})];
let id_list = memberList.map(item => item.user_id);
ProjectBackend.GetUserList(id_list).then(res => {
let userInfo_list = Object.values(res.data);
memberList = memberList.map((item, index) => {
item.info = userInfo_list[index];
return item;
});
this.setState({
memberList,
loadingState: false,
});
});
// var res1 = {
// "operation_code": 1000,
// "message": "",
// "data": {
// "2ab2770e-b6e7-476b-969c-2db815e878e6": {
// "Id": "62e4b104b686c0cf874cf17a",
// "CreateAt": "2022-07-30T04:18:12.557Z",
// "UpdateAt": "2022-07-30T04:18:12.557Z",
// "uuid": "2ab2770e-b6e7-476b-969c-2db815e878e6",
// "profile": {
// "name": "string",
// "age": 0,
// "locaion": "string",
// "email": "string",
// "phone": "string",
// "gender": true,
// "organization": "",
// "degree": "string",
// "position": "string",
// "employer": "string",
// "major": "string",
// },
// "password": "a6ba7732dfb7c19d1520b1eb0386d1a2",
// "salt": "371832835a7ea8c50f44c8c8ab333c55",
// },
// },
// };
// let userInfo_list = Object.values(res1.data);
// memberList = memberList.map((item, index) => {
// item.info = userInfo_list[index].profile;
// return item;
// });
// this.setState({
// memberList,
// loadingState: false,
// });
});
}
render() {
return (
<div className="build-team-page" data-component="build-team-page">
<ModalCard
title="项目成员"
right={(
<Button type="primary" size="small" onClick={() => {
this.setState({
addMemberForm: Object.assign(this.state.addMemberForm, {show: true}),
});
}}>添加成员</Button>
)}
>
<div className="member-list">
<Table
key="admins"
columns={this.columns}
rowKey="Id"
pagination={false}
dataSource={this.state.memberList}
size="small"
loading={this.state.loadingState}
/>
</div>
</ModalCard>
<Modal
title="角色分配修改"
visible={this.state.roleChangeForm.show}
okText="确认修改"
cancelText="取消修改"
closable={!this.state.roleChangeForm.updateLoading}
keyboard={!this.state.roleChangeForm.updateLoading}
maskClosable={!this.state.roleChangeForm.updateLoading}
confirmLoading={this.state.roleChangeForm.updateLoading}
// onOk={() => {
// this.setState({
// roleChangeForm: Object.assign(this.state.roleChangeForm, {updateLoading: true})
// });
// request({
// url: baseURL + "/review/proj/assign",
// // url:"http://49.232.73.36:8081/review/proj/assign",
// method: "PATCH",
// data: {
// assignment_id: this.state.roleChangeForm.assignment_id,
// new_role: this.state.roleChangeForm.new_role,
// }
// }).then(res => {
// console.log(res);
// this.setState({
// roleChangeForm: Object.assign(this.state.roleChangeForm, {updateLoading: false, show: false})
// });
// this.getProjectMember();
// message.success("修改成功!");
// }).catch(err => {
// message.error(err.message || "请求错误!");
// this.setState({
// roleChangeForm: Object.assign(this.state.roleChangeForm, {updateLoading: false})
// });
// });
// }}
onOk={() => { }}
onCancel={() => {
if (this.state.roleChangeForm.updateLoading) {
message.error("修改中,操作不可中断!");
} else {
this.setState({
roleChangeForm: Object.assign(this.state.roleChangeForm, {show: false}),
});
}
}}
>
<label style={{lineHeight: ".6rem"}}>新的角色</label>
<Select value={this.state.roleChangeForm.new_role} style={{width: "100%"}} onChange={(e) => {
this.setState({
roleChangeForm: Object.assign(this.state.roleChangeForm, {new_role: e}),
});
}}>
<Option value={1}>管理员</Option>
<Option value={2}>专家</Option>
<Option value={3}>学科助理</Option>
<Option value={4}>教师</Option>
<Option value={5}>外审人员</Option>
</Select>
</Modal>
<Modal
title="添加成员"
visible={this.state.addMemberForm.show}
okText="确认添加"
cancelText="取消添加"
closable={!this.state.addMemberForm.loadingState}
keyboard={!this.state.addMemberForm.loadingState}
maskClosable={!this.state.addMemberForm.loadingState}
confirmLoading={this.state.addMemberForm.loadingState}
onOk={() => {
}}
onCancel={() => {
if (this.state.addMemberForm.loadingState) {
message.error("添加中,操作不可中断!");
} else {
this.setState({
addMemberForm: Object.assign(this.state.addMemberForm, {show: false}),
});
}
}}
>
<label style={{lineHeight: ".6rem"}}>查找用户</label>
<Search placeholder="请输入被添加用户账号" onSearch={() => {
}} enterButton />
{
this.state.addMemberForm.user_id === "" ? "" : (
<>
<label style={{lineHeight: ".6rem"}}>角色分配</label>
<Select value={this.state.addMemberForm.role} style={{width: "100%"}} onChange={(e) => {
this.setState({
addMemberForm: Object.assign(this.state.addMemberForm, {role: e}),
});
}}>
<Option value={1}>管理员</Option>
<Option value={2}>专家</Option>
<Option value={3}>学科助理</Option>
<Option value={4}>教师</Option>
<Option value={5}>外审人员</Option>
</Select>
</>
)
}
</Modal>
<Modal
title="发送邮件"
visible={this.state.emailForm.show}
okText="确认发送"
cancelText="取消发送"
closable={!this.state.emailForm.loadingState}
keyboard={!this.state.emailForm.loadingState}
maskClosable={!this.state.emailForm.loadingState}
confirmLoading={this.state.emailForm.loadingState}
// onOk={() => {
// this.emailFormRef.current.validateFields().then(formData => {
// let data = {
// body: {
// message: formData.message,
// send_time: this.state.emailForm.send_time,
// sender: this.state.emailForm.sender
// },
// destination: this.state.emailForm.destination,
// subject: formData.subject
// };
// this.setState({
// emailForm: Object.assign(this.state.emailForm, {loadingState: true})
// });
// request({
// url: baseURL + "/review/noticer/email",
// // url:"http://49.232.73.36:8081/review/noticer/email",
// method: "POST",
// data
// }).then(res => {
// this.setState({
// emailForm: Object.assign(this.state.emailForm, {loadingState: false, show: false})
// });
// this.emailFormRef.current.resetFields();
// message.success("发送成功");
// }).catch(err => {
// this.setState({
// emailForm: Object.assign(this.state.emailForm, {loadingState: false})
// });
// message.error(err.message || "请求错误");
// });
// }).catch(err => {
// message.warning("请正确填写邮件内容");
// });
// }}
onOk={() => { }}
onCancel={() => {
if (this.state.emailForm.loadingState) {
message.error("发送中,操作不可中断!");
} else {
this.emailFormRef.current.resetFields();
this.setState({
emailForm: Object.assign(this.state.emailForm, {show: false}),
});
}
}}
>
<Form
name="emailForm"
labelCol={{span: 4}}
wrapperCol={{span: 20}}
initialValues={this.state.emailForm}
autoComplete="off"
ref={this.emailFormRef}
>
<Form.Item
label="邮件主题"
name="subject"
rules={[{required: true, message: "请输入邮件主题"}]}
>
<Input placeholder="请输入邮件主题" />
</Form.Item>
<Form.Item
label="邮件内容"
name="message"
rules={[{required: true, message: "请输入邮件内容"}]}
>
<TextArea placeholder="请输入邮件内容" autoSize={{minRows: 2, maxRows: 6}} />
</Form.Item>
</Form>
</Modal>
</div>
);
}
}
export default withRouter(index);

10
web/src/BuildTeam.less Normal file
View File

@ -0,0 +1,10 @@
[data-component=build-team-page]{
width: 100%;
min-height: 6rem;
background-color: white;
.member-list{
width: 100%;
box-sizing: border-box;
padding: 0px .2rem;
}
}

106
web/src/CalendarButton.js Normal file
View File

@ -0,0 +1,106 @@
import React, {Component} from "react";
import {Button, Calendar, Col, Row, Select} from "antd";
import "./CalendarButton.less";
/**
* @description: 参数
* @icon 按钮icon
* @label 按钮名称
*/
export default class index extends Component {
state = {
calendarVisible: false,
}
render() {
return (
<div className="calendar-button-box" data-component="calendar-button-box">
<Button size="small" icon={this.props.icon} onClick={() => {
this.setState({
calendarVisible: true,
});
}}>{this.props.label}</Button>
<div className="calendar-box" style={{maxHeight: this.state.calendarVisible ? "5rem" : 0}}>
<Calendar
fullscreen={false}
headerRender={({value, type, onChange, onTypeChange}) => {
const start = 0;
const end = 12;
const monthOptions = [];
const current = value.clone();
const localeData = value.localeData();
const months = [];
for (let i = 0; i < 12; i++) {
current.month(i);
months.push(localeData.monthsShort(current));
}
for (let index = start; index < end; index++) {
monthOptions.push(
<Select.Option className="month-item" key={`${index}`}>
{months[index]}
</Select.Option>
);
}
const month = value.month();
const year = value.year();
const options = [];
for (let i = year - 10; i < year + 10; i += 1) {
options.push(
<Select.Option key={i} value={i} className="year-item">
{i}
</Select.Option>
);
}
return (
<div style={{padding: 8}}>
<Row gutter={8} justify="end">
<Col>
<Select
size="small"
dropdownMatchSelectWidth={false}
className="my-year-select"
onChange={newYear => {
const now = value.clone().year(newYear);
onChange(now);
}}
value={String(year)}
>
{options}
</Select>
</Col>
<Col>
<Select
size="small"
dropdownMatchSelectWidth={false}
value={String(month)}
onChange={selectedMonth => {
const newValue = value.clone();
newValue.month(parseInt(selectedMonth, 10));
onChange(newValue);
}}
>
{monthOptions}
</Select>
</Col>
</Row>
</div>
);
}}
onSelect={(_date) => {
let date = new Date(_date._d);
this.setState({
calendarVisible: false,
});
this.props.onDateChange(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`);
}}
/>
</div>
</div>
);
}
}

View File

@ -0,0 +1,21 @@
[data-component=calendar-button-box]{
position: relative;
width: max-content;
height: max-content;
> .calendar-box{
width: auto;
max-height: 0;
overflow: hidden;
transition: all .2s;
position: absolute;
background-color: white;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
top: .34rem;
left: 50%;
transform: translateX(-50%);
z-index: 600;
.ant-picker-calendar-date-value,th{
font-size: .14rem;
}
}
}

143
web/src/ChangeTags.js Normal file
View File

@ -0,0 +1,143 @@
import React, {Component} from "react";
import {Input, Tag, Tooltip} from "antd";
import {PlusOutlined} from "@ant-design/icons";
import "./ChangeTags.less";
export default class index extends Component {
state = {
tags: [],
inputVisible: false,
inputValue: "",
editInputIndex: -1,
editInputValue: "",
};
handleClose = (removedTag) => {
const tags = this.state.tags.filter((tag) => tag !== removedTag);
this.props.onChange(tags);
this.setState({tags});
};
showInput = () => {
this.setState({inputVisible: true}, () => this.input.focus());
};
handleInputChange = (e) => {
this.setState({inputValue: e.target.value});
};
handleInputConfirm = () => {
const {inputValue} = this.state;
let {tags} = this.state;
if (inputValue && tags.indexOf(inputValue) === -1) {
tags = [...tags, inputValue];
}
this.props.onChange(tags);
this.setState({
tags,
inputVisible: false,
inputValue: "",
});
};
handleEditInputChange = (e) => {
this.setState({editInputValue: e.target.value});
};
handleEditInputConfirm = () => {
this.setState(({tags, editInputIndex, editInputValue}) => {
const newTags = [...tags];
newTags[editInputIndex] = editInputValue;
return {
tags: newTags,
editInputIndex: -1,
editInputValue: "",
};
});
};
saveInputRef = (input) => {
this.input = input;
};
saveEditInputRef = (input) => {
this.editInput = input;
};
render() {
const {tags, inputVisible, inputValue, editInputIndex, editInputValue} =
this.state;
return (
<>
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<Input
ref={this.saveEditInputRef}
key={tag}
size="small"
className="tag-input"
value={editInputValue}
onChange={this.handleEditInputChange}
onBlur={this.handleEditInputConfirm}
onPressEnter={this.handleEditInputConfirm}
/>
);
}
const isLongTag = tag.length > 20;
const tagElem = (
<Tag
className="edit-tag"
key={tag}
closable={true}
onClose={() => this.handleClose(tag)}
>
<span
onDoubleClick={(e) => {
if (index !== 0) {
this.setState(
{editInputIndex: index, editInputValue: tag},
() => {
this.editInput.focus();
}
);
e.preventDefault();
}
}}
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag} key={tag}>
{tagElem}
</Tooltip>
) : (
tagElem
);
})}
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
className="tag-input"
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && (
<Tag className="site-tag-plus" onClick={this.showInput}>
<PlusOutlined /> 添加
</Tag>
)}
</>
);
}
}

14
web/src/ChangeTags.less Normal file
View File

@ -0,0 +1,14 @@
.site-tag-plus {
background: #fff;
border-style: dashed;
}
.edit-tag {
user-select: none;
}
.tag-input {
width: 78px;
margin-right: 8px;
vertical-align: top;
}

View File

@ -0,0 +1,190 @@
import React, {Component} from "react";
import {withRouter} from "react-router-dom";
import {Button, Col, Row, Select, Slider, Spin, Tag, message} from "antd";
import BraftEditor from "braft-editor";
import "braft-editor/dist/index.css";
import "./ChoiceQuestionEditer.less";
import * as PropositionBackend from "./backend/PropositionBackend";
const {Option} = Select;
class ChoiceQuestionEditer extends Component {
state = {
editorState: BraftEditor.createEditorState(null),
loadingState: false,
questionParams: {
subject: "",
difficulty: 1,
answer: "",
},
}
upLoadQuestion = (uid) => {
this.setState({
loadingState: true,
});
let data = {
advanced_props: {
ctt_diff_1: this.state.questionParams.difficulty,
ctt_diff_2: this.state.questionParams.difficulty,
ctt_level: this.state.questionParams.difficulty,
irt_level: this.state.questionParams.difficulty,
},
apply_record: {
grade_fits: this.props.grade_range.join(","),
participant_count: 0,
test_count: 0,
test_region: [],
test_year: `${new Date().getFullYear()}`,
},
author: uid,
basic_props: {
ability_dimension: this.props.ability.join(","),
description: "暂无",
details: this.state.editorState.toHTML(),
details_dimension: this.props.content.join(","),
encode: "",
keywords: [],
sub_ability_dimension: "",
sub_details_dimension: "",
subject: this.state.questionParams.subject,
subject_requirements: "",
},
extra_props: {
is_question_group: false,
is_scene: true,
material_length: 0,
reading_material_topic: "",
},
info: {
answer: this.state.questionParams.answer,
body: this.state.editorState.toHTML(),
solution: "无",
title: "无",
type: "选择题",
},
source_project: this.props.projectId,
spec_props: {
article_type: "无",
length: "无",
topic: "无",
},
};
PropositionBackend.CreateNewQuestion(data).then(res => {
this.setState({
loadingState: false,
});
}).then(res => {
this.props.history.goBack();
message.success("上传成功");
}).catch(err => {
this.setState({
loadingState: false,
});
message.error(err.message || "请求错误");
});
}
componentDidMount() {
this.setState({
questionParams: Object.assign(this.state.questionParams, {subject: this.props.defaultSubjectValue}),
});
}
render() {
return (
<div className="choice-question-editer" data-component="choice-question-editer" id="choice-question-edit-box">
<Spin spinning={this.state.loadingState} tip="上传试题中">
<Row>
<BraftEditor
value={this.state.editorState}
onChange={(editorState) => {
this.setState({editorState});
}}
onSave={() => {
console.log("保存题目");
}}
/>
</Row>
<Row className="question-params">
<div className="title">
<span>参数编辑</span>
</div>
<Row className="param-item" style={{marginTop: ".17rem"}}>
<Col span="4" className="label">
<span>学科</span>
</Col>
<Col span="20" className="value">
<Select
placeholder="选择学科"
value={this.state.questionParams.subject}
defaultValue={this.props.defaultSubjectValue}
onSelect={(e) => {
let questionParams = Object.assign(this.state.questionParams, {subject: e});
this.setState({
questionParams,
});
}}
size="small"
>
{
this.props.subjectList.map((item, index) => (
<Option value={item} key={index + Math.random(100)}>{item}</Option>
))
}
</Select>
</Col>
</Row>
<Row className="param-item" style={{marginTop: ".17rem"}}>
<Col span="4" className="label">
<span>难度</span>
</Col>
<Col span="20" className="value">
<Slider marks={{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}} step={null} defaultValue={1} max={5} min={1} onChange={(e) => {
let questionParams = Object.assign(this.state.questionParams, {difficulty: e});
this.setState({
questionParams,
});
}} />
</Col>
</Row>
<Row className="param-item" style={{marginTop: ".3rem"}}>
<Col span="4" className="label">
<span>能力纬度</span>
</Col>
<Col span="20" className="value">
<div className="tag-list">
{
this.props.ability.map(item => (
<Tag key={item.id}>{item}</Tag>
))
}
</div>
</Col>
</Row>
<Row className="param-item" style={{marginTop: ".17rem"}}>
<Col span="4" className="label">
<span>内容纬度</span>
</Col>
<Col span="20" className="value">
<div className="tag-list">
{
this.props.content.map(item => (
<Tag key={item.id}>{item}</Tag>
))
}
</div>
</Col>
</Row>
</Row>
<div className="question-complete-box">
<Button type="primary" block onClick={() => {
this.upLoadQuestion(this.props.author);
}}>完成编辑</Button>
</div>
</Spin>
</div>
);
}
}
export default withRouter(ChoiceQuestionEditer);

View File

@ -0,0 +1,48 @@
[data-component=choice-question-editer]{
width: 100%;
height: auto;
.question-params{
> .title{
width: 100%;
height: .41rem;
background-color: #EEEEEE;
line-height: .41rem;
box-sizing: border-box;
padding-left: .1rem;
> span{
font-size: .14rem;
color: #000000;
}
}
> .param-item{
width: 100%;
height: .32rem;
.label{
box-sizing: border-box;
padding-left: .1rem;
line-height: .32rem;
> span{
font-size: .14rem;
color: #000000;
}
}
.value{
> .ant-select{
width: 100%;
}
> .tag-list{
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
}
}
}
.question-complete-box{
width: 100%;
margin-top: .77rem;
margin-bottom: .5rem;
}
}

102
web/src/CompletionStatus.js Normal file
View File

@ -0,0 +1,102 @@
import React, {Component} from "react";
import {Col, Row, Statistic} from "antd";
import ModulaCard from "./ModulaCard";
import "./CompletionStatus.less";
export default class index extends Component {
state = {
status: {},
loadingState: false,
}
getStatusList = () => {
this.setState({
loadingState: true,
});
// request({
// url:baseURL+`/review/proj/step/stat/${this.props.stepId}`,
// // url:`http://49.232.73.36:8081/review/proj/step/stat/${this.props.stepId}`,
// method:"GET"
// }).then(res => {
// this.setState({
// status:res.data,
// loadingState:false
// });
// }).catch(err => {
// this.setState({
// loadingState:false
// });
// });
var res = {
"operation_code": 1000,
"message": "",
"data": {
"pass": 0,
"pass_rate": 0,
"return": 0,
"to_audit": 0,
"to_correct": 0,
"to_upload": 0,
"total": 0,
},
};
this.setState({
status: res.data,
loadingState: false,
});
}
componentDidMount() {
this.getStatusList();
}
render() {
return (
<ModulaCard title="完成情况">
{
this.state.loadingState ? (
<></>
) : (
<div className="completion-status-box" data-component="completion-status-box">
<div className="left-box">
<div className="title">
<span>{this.props.title}</span>
</div>
<div className="value-list">
<Row gutter={16}>
<Col span={12}>
<Statistic title="材料总计" value={this.state.status.total} />
</Col>
<Col span={12}>
<Statistic title="通过率" value={this.state.status.pass_rate} suffix="%" />
</Col>
</Row>
</div>
</div>
<div className="right-box">
<Row gutter={16} style={{width: "4.2rem"}}>
<Col span={4}>
<Statistic title={<div className="statistic-item-title"><div className="circle" style={{backgroundColor: "#87D068"}}></div></div>} value={this.state.status.pass} />
</Col>
<Col span={4}>
<Statistic title={<div className="statistic-item-title"><div className="circle" style={{backgroundColor: "#FF5500"}}></div></div>} value={this.state.status.return} />
</Col>
<Col span={5}>
<Statistic title={<div className="statistic-item-title"><div className="circle" style={{backgroundColor: "#FF5500"}}></div></div>} value={this.state.status.to_upload} />
</Col>
<Col span={5}>
<Statistic title={<div className="statistic-item-title"><div className="circle" style={{backgroundColor: "#2DB7F5"}}></div></div>} value={this.state.status.to_audit} />
</Col>
<Col span={5}>
<Statistic title={<div className="statistic-item-title"><div className="circle" style={{backgroundColor: "#2DB7F5"}}></div></div>} value={this.state.status.to_correct} />
</Col>
</Row>
</div>
</div>
)
}
</ModulaCard>
);
}
}

View File

@ -0,0 +1,51 @@
[data-component=completion-status-box]{
width: 100%;
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding-left: 0.47rem;
> .left-box{
> .title{
height: .24rem;
line-height: .24rem;
margin-bottom: .08rem;
> span{
font-size: .14rem;
color: #8C8C8C;
}
}
> .value-list{
.ant-statistic-title{
font-size: .14rem;
color: rgba(0, 0, 0, 0.45);
}
.ant-statistic-content-value-int,.ant-statistic-content-suffix{
font-size: .26rem;
color: rgba(0, 0, 0, 0.85);
}
}
}
> .right-box{
.statistic-item-title{
font-size: .14rem;
color: #000000D9;
line-height: .22rem;
box-sizing: border-box;
padding-left: .14rem;
position: relative;
> .circle{
width: .06rem;
height: .06rem;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
border-radius: 50%;
}
}
.ant-statistic-content-value-int,.ant-statistic-content-suffix{
font-size: .26rem;
color: rgba(0, 0, 0, 0.85);
}
}
}

477
web/src/CreatePaper.js Normal file
View File

@ -0,0 +1,477 @@
import React, {Component} from "react";
import {Button, Col, Descriptions, Input, Modal, PageHeader, Pagination, Row, Select, Slider, Spin, Tag, message} from "antd";
import UpLoadQuestionModal from "./UpLoadQuestionModal";
import HistoryQuestion from "./HistoryQuestion";
import BraftEditor from "braft-editor";
import "braft-editor/dist/index.css";
import "./CreatePaper.less";
import * as ProjectBackend from "./backend/ProjectBackend";
import * as PropositionBackend from "./backend/PropositionBackend";
const {Option} = Select;
const {Search} = Input;
export default class CreatePaper extends Component {
state = {
initLoading: true,
createTime: 0,
projectInfo: {},
upLoadQuestionModalParams: {
show: false,
type: "update-paper",
},
questionList: [{
state: "edit", // 表示编辑中
subject: "",
difficulty: 1,
ability: this.props.match.params.ability,
content: this.props.match.params.content,
body: BraftEditor.createEditorState(null),
}],
paperViewVisible: false,
loadingState: false,
}
componentDidMount() {
let t = new Date();
this.setState({
createTime: `${t.getFullYear()}-${t.getMonth().toString().padStart(2, "0")}-${t.getDate().toString().padStart(2, "0")} ${t.getHours().toString().padStart(2, "0")}:${t.getMinutes().toString().padStart(2, "0")}:${t.getSeconds().toString().padStart(2, "0")}`,
});
this.getProjectInfo();
}
// upLoadQuestion = (info) => {
// this.setState({
// loadingState: true
// });
// return request({
// url: baseURL1 + "/qbank/question",
// // url:"http://49.232.73.36:8082/qbank/question",
// method: "POST",
// data: {
// advanced_props: {
// ctt_diff_1: info.difficulty,
// ctt_diff_2: info.difficulty,
// ctt_level: info.difficulty,
// irt_level: info.difficulty
// },
// apply_record: {
// grade_fits: this.state.projectInfo.basic_info.grade_range.join(","),
// participant_count: 0,
// test_count: 0,
// test_region: [],
// test_year: `${new Date().getFullYear()}`,
// },
// author: store.getState().userInfo.Id,
// basic_props: {
// ability_dimension: info.ability,
// description: "暂无",
// details: info.body.toHTML(),
// details_dimension: info.content,
// encode: "",
// keywords: [],
// sub_ability_dimension: "",
// sub_details_dimension: "",
// subject: info.subject,
// subject_requirements: ""
// },
// extra_props: {
// is_question_group: false,
// is_scene: true,
// material_length: 0,
// reading_material_topic: ""
// },
// info: {
// answer: "",
// body: info.body.toHTML(),
// solution: "无",
// title: "无",
// type: "选择题"
// },
// source_project: this.props.match.params.project,
// spec_props: {
// article_type: "无",
// length: "无",
// topic: "无"
// }
// }
// });
// }
upLoadQuestion = (info) => {
this.setState({
loadingState: true,
});
let data = {
advanced_props: {
ctt_diff_1: info.difficulty,
ctt_diff_2: info.difficulty,
ctt_level: info.difficulty,
irt_level: info.difficulty,
},
apply_record: {
grade_fits: this.state.projectInfo.basic_info.grade_range.join(","),
participant_count: 0,
test_count: 0,
test_region: [],
test_year: `${new Date().getFullYear()}`,
},
author: this.props.match.params.uid,
basic_props: {
ability_dimension: info.ability,
description: "暂无",
details: info.body.toHTML(),
details_dimension: info.content,
encode: "",
keywords: [],
sub_ability_dimension: "",
sub_details_dimension: "",
subject: info.subject,
subject_requirements: "",
},
extra_props: {
is_question_group: false,
is_scene: true,
material_length: 0,
reading_material_topic: "",
},
info: {
answer: "",
body: info.body.toHTML(),
solution: "无",
title: "无",
type: "选择题",
},
source_project: this.props.match.params.project,
spec_props: {
article_type: "无",
length: "无",
topic: "无",
},
};
return PropositionBackend.CreateNewQuestion(data);
}
getProjectInfo = () => {
let newParms = this.props.match.params;
ProjectBackend.GetDetailedInfo(newParms.uid + "/" + newParms.project).then(res => {
this.setState({
projectInfo: res.data.basic_info,
initLoading: false,
});
}).catch(err => {
this.setState({
initLoading: false,
});
this.props.history.goBack();
message.error("编辑器加载失败!");
});
}
render() {
return (
<div className="create-paper-page" data-component="create-paper-page">
<PageHeader
ghost={false}
onBack={() => this.props.history.goBack()}
title="命题组卷"
subTitle="上传试题"
extra={[
<Button key="1" onClick={() => {
this.setState({
upLoadQuestionModalParams: {
show: true,
type: "update-paper",
},
});
}}>编辑内容</Button>,
]}
>
{
this.state.initLoading ? (
<Spin spinning={true} tip="初始化中"></Spin>
) : (
<Descriptions size="small" column={3}>
<Descriptions.Item label="创建时间" key="createAt">{this.state.createTime}</Descriptions.Item>
<Descriptions.Item label="项目" key="peojects">{this.state.projectInfo.basic_info.name}</Descriptions.Item>
<Descriptions.Item label="学科" key="subjects">{
this.state.projectInfo.basic_info.subjects.map((item, index) => (
<span key={index}>{item}{index === this.state.projectInfo.basic_info.subjects.length - 1 ? "" : "、"}</span>
))
}</Descriptions.Item>
<Descriptions.Item label="内容纬度" key="content">{
this.props.match.params.content.split(",").map((item, index) => (
<span key={index}>{item}{index === this.props.match.params.content.split(",").length - 1 ? "" : "、"}</span>
))
}</Descriptions.Item>
<Descriptions.Item label="能力纬度" key="ability">
{
this.props.match.params.ability.split(",").map((item, index) => (
<span key={index}>{item}{index === this.props.match.params.ability.split(",").length - 1 ? "" : "、"}</span>
))
}
</Descriptions.Item>
</Descriptions>
)
}
</PageHeader>
<Spin spinning={this.state.loadingState} tip="上传中">
<Row className="container">
<Col span="8" className="left-box">
<div className="title-box">
<div className="title-value">
<div className="ver-line"></div>
<div className="value">
<span>相关题库</span>
</div>
</div>
<Search placeholder="input search text" style={{width: "2.64rem"}} size="middle" />
</div>
<HistoryQuestion />
<HistoryQuestion />
<HistoryQuestion />
<Pagination defaultCurrent={1} total={50} className="page-spare" />
</Col>
<Col span="16" className="right-box">
{
this.state.questionList.map((item, index) => (
<Row className="question-item" key={index + Math.random(100)} gutter={[8, 8]}>
<Col className="info-box" span="18">
<Row className="main-box">
{
item.state === "edit" ? (
<BraftEditor
value={item.body}
onChange={(editorState) => {
let questionList = [...this.state.questionList];
questionList[index].body = editorState;
this.setState({questionList});
}}
onSave={() => {
console.log("保存题目");
}}
/>
) : (
<div className="view-box" dangerouslySetInnerHTML={{__html: item.body.toHTML()}}>
</div>
)
}
</Row>
<Row className="btn-line">
<Button type="primary" size="small" style={{width: "1rem", marginRight: ".2rem"}} onClick={() => {
let questionList = [...this.state.questionList];
questionList[index].state = item.state === "edit" ? "complete" : "edit";
this.setState({
questionList,
});
}}>
{
item.state === "edit" ? "保存" : "编辑"
}
</Button>
<Button type="primary" danger size="small" style={{width: "1rem"}} onClick={() => {
let questionList = [...this.state.questionList];
questionList.splice(index, 1);
this.setState({
questionList,
});
}}>
删除
</Button>
</Row>
</Col>
<Col className="params-box" span="6">
<div className="title">
<span>参数编辑</span>
</div>
<Row className="params-item">
<Col className="label" span="8">
<span>学科</span>
</Col>
<Col className="value" span="16">
<Select
placeholder="选择学科"
value={item.subject}
defaultValue={this.props.match.params.subject}
onSelect={(e) => {
let questionList = [...this.state.questionList];
questionList[index].subject = e;
this.setState({
questionList,
});
}}
size="small"
>
{
this.state.initLoading ? "" : this.state.projectInfo.basic_info.subjects.map((item, index) => (
<Option value={item} key={index + Math.random(100)}>{item}</Option>
))
}
</Select>
</Col>
</Row>
<Row className="params-item" style={{marginTop: ".17rem"}}>
<Col span="8" className="label">
<span>难度</span>
</Col>
<Col span="16" className="value">
<Slider marks={{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}} step={null} value={item.difficulty} defaultValue={1} max={5} min={1} onChange={(e) => {
let questionList = [...this.state.questionList];
questionList[index].difficulty = e;
this.setState({
questionList,
});
}} />
</Col>
</Row>
<Row className="params-item" style={{marginTop: ".17rem"}}>
<Col span="8" className="label">
<span>能力纬度</span>
</Col>
<Col span="16" className="value">
<div className="tag-list">
{
item.ability.split(",").map(item => (
<Tag key={item.Id}>{item}</Tag>
))
}
</div>
</Col>
</Row>
<Row className="params-item" style={{marginTop: ".1rem"}}>
<Col span="8" className="label">
<span>内容纬度</span>
</Col>
<Col span="16" className="value">
<div className="tag-list">
{
item.content.split(",").map(item => (
<Tag Key={item.Id}>{item}</Tag>
))
}
</div>
</Col>
</Row>
</Col>
<Row gutter={[8, 8]} className="oper-btn-line">
<Col span="18" className="left">
{
index === this.state.questionList.length - 1 ? (
<Button type="primary" size="large" onClick={() => {
let questionList = [...this.state.questionList];
questionList.push({
state: "edit", // 表示编辑中
subject: "",
difficulty: 1,
ability: this.props.match.params.ability,
content: this.props.match.params.content,
body: BraftEditor.createEditorState(null),
});
this.setState({
questionList,
});
}}>
添加一题
</Button>
) : ""
}
</Col>
<Col span="6" className="right">
<Button size="large" style={{backgroundColor: "#EEEEEE"}} onClick={() => {
this.setState({
paperViewVisible: true,
});
}}>
预览试卷
</Button>
<Button type="primary" size="large" style={{width: "1.2rem"}} onClick={async() => {
let questionIdList = [];
for (let i = 0; i < this.state.questionList.length; i++) {
await this.upLoadQuestion(this.state.questionList[i]).then((res) => {
questionIdList.push({
comment: "暂无",
question_id: res.data,
score: 0,
});
}).catch(err => {
this.setState({
loadingState: false,
});
message.error(err.message || "试题上传失败,请重试");
});
}
let data = {
author: this.props.match.params.uid,
info: [{
description: "暂无",
question_list: questionIdList,
score: 0,
title: "无",
}],
props: {
difficulty: "1",
grade_range: this.state.projectInfo.basic_info.grade_range,
subjects: this.state.projectInfo.basic_info.subjects,
time_limit: "0",
},
source_project: this.props.match.params.project,
title: "无",
};
PropositionBackend.CreateNewTestpaper(data).then(res => {
this.setState({
loadingState: false,
});
this.props.history.goBack();
message.success("试题上传成功");
}).catch(err => {
this.setState({
loadingState: false,
});
message.error(err.message || "试题上传失败,请重试");
this.props.history.goBack();
});
}}>
保存
</Button>
</Col>
</Row>
</Row>
))
}
</Col>
</Row>
</Spin>
<UpLoadQuestionModal
{...this.state.upLoadQuestionModalParams}
onClose={() => {
this.setState({
upLoadQuestionModalParams: {
show: false,
type: "update-paper",
},
});
}}
/>
<Modal width={1200} title="试卷预览" visible={this.state.paperViewVisible} onOk={() => {
this.setState({
paperViewVisible: false,
});
}} onCancel={() => {
this.setState({
paperViewVisible: false,
});
}}>
<div className="view-box">
{
this.state.questionList.map(item => (
<div className="question-view-item" dangerouslySetInnerHTML={{__html: item.body.toHTML()}} key={item.Id} ></div>
))
}
</div>
</Modal>
</div>
);
}
}

120
web/src/CreatePaper.less Normal file
View File

@ -0,0 +1,120 @@
[data-component=create-paper-page]{
width: 100%;
.container{
width: 100%;
box-sizing: border-box;
> .left-box{
background-color: white;
box-sizing: border-box;
padding: .22rem .1rem .4rem .22rem;
> .title-box{
width: 100%;
height: .32rem;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding-left: .1rem;
padding-right: .24rem;
margin-bottom: .2rem;
> .title-value{
height: 100%;
display: flex;
align-items: center;
> .ver-line{
height: .18rem;
width: .06rem;
background-color: #1890FF;
margin-right: .06rem;
}
> .value{
font-size: .16rem;
color: #000000D9;
}
}
}
.page-spare{
margin: .2rem;
.ant-pagination-item-link,.ant-pagination-item{
background: #91D5FF;
border: 1px solid #FFFEFE;
box-sizing: border-box;
border-radius: 2px;
.anticon{
color: white;
}
}
}
}
> .right-box{
background-color: #EEEEEE;
box-sizing: border-box;
padding: .16rem;
height: 100%;
overflow-y: scroll;
> .question-item{
width: 100%;
height: 100%;
background-color: white;
box-sizing: border-box;
padding: .62rem .73rem 0px .29rem;
overflow-y: scroll;
margin-top: .2rem;
> .params-box{
> .title{
width: 100%;
height: .41rem;
background-color: #EEEEEE;
line-height: .41rem;
box-sizing: border-box;
padding-left: .1rem;
> span{
font-size: .14rem;
color: #000000;
}
}
> .params-item{
width: 100%;
min-height: .32rem;
margin-top: .1rem;
> .label{
box-sizing: border-box;
padding-left: .1rem;
line-height: .32rem;
> span{
font-size: .14rem;
color: #000000;
}
}
> .value{
> .ant-select{
width: 100%;
}
> .tag-list{
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
}
}
}
> .oper-btn-line{
height: 1rem;
width: 100%;
> .right{
display: flex;
justify-content: space-around;
}
}
> .info-box{
> .btn-line{
height: 1rem;
}
}
}
}
}
}

435
web/src/DataTable.js Normal file
View File

@ -0,0 +1,435 @@
import React, {Component} from "react";
import {Button, Modal, Pagination, Radio, Space, Table, Tag, Upload, message} from "antd";
import ModulaCard from "./ModulaCard";
import ModifyRecordModal from "./ModifyRecordModal";
import {FileTextTwoTone, UploadOutlined} from "@ant-design/icons";
import "./DataTable.less";
export default class index extends Component {
state = {
data: [],
loadingState: false,
reviewResultsVisible: false,
statusChangeLoading: false,
modifyRecordVisible: false,
selectedSubmitId: "",
statusChangeParams: {
value: 1,
submitId: "",
},
}
modifyRecordRef = React.createRef()
colums = [[{
title: "上传时间",
key: "CreateAt",
align: "center",
width: 150,
render: (text, record) => (
<span>{this.dateFilter(record.CreateAt)}</span>
),
}, {
title: "上传用户",
dataIndex: "user",
key: "user",
align: "center",
width: 120,
}, {
title: "评审材料",
key: "contents",
align: "center",
width: 220,
render: (text, record) => (
<Space size="middle">
{
record.contents ? (
<span style={{cursor: "pointer"}} onClick={this.downLoadFile.bind(this, record.contents[0].item_id)}>{record.contents[0].comment} <FileTextTwoTone /></span>
) : "无"
}
</Space>
),
}, {
title: "评审结果",
key: "status",
align: "center",
width: 100,
render: (text, record) => {
let levelList = [{
mode: "等待审核",
color: "default",
}, {
mode: "通过",
color: "#87D068",
}, {
mode: "再修改",
color: "#2DB7F5",
}, {
mode: "驳回",
color: "#FF5500",
}];
return (
<Space size="middle">
<Tag color={levelList[record.status].color} onClick={() => {
this.setState({
reviewResultsVisible: true,
statusChangeParams: {
submitId: record.uuid,
value: record.status,
},
});
}} style={{cursor: "pointer"}}>{levelList[record.status].mode}</Tag>
</Space>
);
},
}, {
title: "反馈批注材料",
dataIndex: "feedback-material",
key: "feedback-material",
align: "center",
render: (text, record) => {
if (!record.feedbackMaterial) {
return (
<Space size="middle">
<Upload>
<Button icon={<UploadOutlined />} size="small">上传批注</Button>
</Upload>
</Space>
);
}
return (
<Space size="middle">
<span>{record.feedbackMaterial}</span><FileTextTwoTone />
</Space>
);
},
}], [{
title: "材料编号",
dataIndex: "uuid",
key: "id",
align: "center",
}, {
title: "上传时间",
key: "date",
align: "center",
render: (text, record) => (
<Space size="middle">
<span>{this.dateFilter(record.CreateAt)}</span>{record.isDelay ? (<Tag color="error"></Tag>) : ""}
</Space>
),
}, {
title: "评审材料",
key: "review-materials",
width: 220,
align: "center",
render: (text, record) => (
<Space size="middle">
{
record.contents ? (
<span style={{cursor: "pointer"}} onClick={this.downLoadFile.bind(this, record.contents[0].item_id)}>{record.contents[0].comment} <FileTextTwoTone /></span>
) : "无"
}
</Space>
),
}, {
title: "评审结果",
key: "result",
align: "center",
render: (text, record) => {
let levelList = [{
mode: "等待评审",
color: "default",
}, {
mode: "通过",
color: "#87D068",
}, {
mode: "再修改",
color: "#2DB7F5",
}, {
mode: "驳回",
color: "#FF5500",
}];
return (
<Space size="middle">
<Tag color={levelList[record.status].color} style={{cursor: "pointer"}}>{levelList[record.status].mode}</Tag>
</Space>
);
},
}, {
title: "反馈意见",
key: "feedback",
align: "center",
render: (text, record) => {
if (record.feedback === "") {
return (
<Space size="middle">
<span style={{cursor: "pointer"}}>等待评审...</span>
</Space>
);
}
return (
<Space size="middle">
{/* <span style={{cursor:'pointer'}} onClick={this.downLoadFile}>{record.feedback}</span><FileTextTwoTone /> */}
<span></span>
</Space>
);
},
}, {
title: "修改记录",
key: "isModify",
align: "center",
width: 80,
render: (text, record) => (
<Space size="middle">
{record.contents ? (<Button type="link" onClick={() => {
this.setState({
selectedSubmitId: record.uuid,
modifyRecordVisible: true,
});
this.modifyRecordRef.current.getRecordList();
}}>查看</Button>) : (<span></span>)}
</Space>
),
}], [{
title: "上传时间",
dataIndex: "CreateAt",
key: "CreateAt",
align: "center",
width: 150,
}, {
title: "上传用户",
dataIndex: "user",
key: "user",
align: "center",
width: 120,
}, {
title: "评审材料",
key: "contents",
align: "center",
width: 220,
render: (text, record) => (
<Space size="middle">
{
record.contents ? (
<span style={{cursor: "pointer"}} onClick={this.downLoadFile.bind(this, record.contents[0].item_id)}>{record.contents[0].comment} <FileTextTwoTone /></span>
) : "无"
}
</Space>
),
}, {
title: "评审结果",
key: "status",
align: "center",
width: 100,
render: (text, record) => {
let levelList = [{
mode: "等待审核",
color: "default",
}, {
mode: "通过",
color: "#87D068",
}, {
mode: "再修改",
color: "#2DB7F5",
}, {
mode: "驳回",
color: "#FF5500",
}];
return (
<Space size="middle">
<Tag color={levelList[record.status].color} style={{cursor: "pointer"}}>{levelList[record.status].mode}</Tag>
</Space>
);
},
}, {
title: "反馈批注材料",
dataIndex: "feedback-material",
key: "feedback-material",
align: "center",
render: (text, record) => {
return (
<Space size="middle">
<span></span>
</Space>
);
},
}],
]
dateFilter(time) {
let date = new Date(time);
return `${date.getFullYear()}-${date.getMonth().toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
}
downLoadFile(file_id) {
message.info(`开始下载文件:${file_id}`);
// request({
// url:baseURL+`/review/file/${file_id}`,
// // url:`http://49.232.73.36:8081/review/file/${file_id}`,
// method: "GET",
// responseType:"blob"
// }).then(res => {
// console.log(res);
// const filename = res.headers["content-disposition"];
// const blob = new Blob([res.data]);
// var downloadElement = document.createElement("a");
// var href = window.URL.createObjectURL(blob);
// downloadElement.href = href;
// downloadElement.download = decodeURIComponent(filename.split("filename*=")[1].replace("utf-8''", ""));
// document.body.appendChild(downloadElement);
// downloadElement.click();
// document.body.removeChild(downloadElement);
// window.URL.revokeObjectURL(href);
// message.success("文件下载成功!");
// }).catch(err => {
// message.error("文件下载失败!");
// });
}
loadClumsIndex = () => {
if (this.props.role === "2") {
return 0;
} else if (this.props.role === "3") {
return 1;
} else if (this.props.role === "4" && this.props.stepName === "测试框架与论证报告") {
return 2;
} else if (this.props.role === "4" && this.props.stepName !== "测试框架与论证报告") {
return 1;
} else {
return 0;
}
}
componentDidMount() {
this.getDataList();
}
getDataList = () => {
this.setState({
loadingState: true,
});
var res = {
"operation_code": 1000,
"message": "",
"data": null,
};
this.setState({
data: res.data,
loadingState: false,
});
// request({method:"GET", url:baseURL+`/review/proj/submits/${this.props.stepId}`}).then(res => {
// // request({ method:'GET', url:`http://49.232.73.36:8081/review/proj/submits/${this.props.stepId}`}).then(res=>{
// console.log(res.data);
// this.setState({
// data:res.data,
// loadingState:false
// });
// }).catch(err => {
// message.error(err.message||"审查材料加载失败!");
// this.setState({
// loadingState:false
// });
// });
}
render() {
return (
<ModulaCard title={this.props.title}>
<div className="data-table-box" data-component="data-table-box">
<Table
dataSource={this.state.data}
columns={this.colums[this.loadClumsIndex()]}
size="small"
rowKey="Id"
pagination={false}
scroll={{y: "5.8rem"}}
loading={this.state.loadingState}
/>
<div className="footer">
<Pagination
total={85}
showTotal={total => `Total ${total} items`}
defaultPageSize={20}
defaultCurrent={1}
size="small"
/>
</div>
</div>
<Modal title="评审意见" visible={this.state.reviewResultsVisible}
cancelText="关闭"
okText="审核"
confirmLoading={this.state.statusChangeLoading}
closable={!this.state.statusChangeLoading}
maskClosable={!this.state.statusChangeLoading}
keyboard={!this.state.statusChangeLoading}
onOk={() => {
// this.setState({
// statusChangeLoading:true
// });
// request({
// url:baseURL+"/review/proj/submit",
// // url:`http://49.232.73.36:8081/review/proj/submit`,
// method: "PUT",
// data:{
// new_status:this.state.statusChangeParams.value,
// submit_id:this.state.statusChangeParams.submitId
// }
// }).then(res => {
// this.setState({
// statusChangeLoading:false,
// reviewResultsVisible:false
// });
// this.getDataList();
// message.success("审核成功");
// }).catch(err => {
// this.setState({
// statusChangeLoading:false
// });
// message.error(err.message||"审核失败");
// });
}}
onCancel={() => {
if (this.state.statusChangeLoading) {
message.warning("请等待");
} else {
this.setState({
reviewResultsVisible: false,
});
}
}}
>
<span>材料评审结果</span>
<Radio.Group name="radiogroup" value={this.state.statusChangeParams.value} onChange={(e) => {
let statusChangeParams = Object.assign(this.state.statusChangeParams, {
value: e.target.value,
});
this.setState({
statusChangeParams,
});
}}>
<Radio value={1}>通过</Radio>
<Radio value={2}>再修改</Radio>
<Radio value={3}>驳回</Radio>
</Radio.Group>
</Modal>
<ModifyRecordModal
show={this.state.modifyRecordVisible}
submitId={this.state.selectedSubmitId}
ref={this.modifyRecordRef}
onCancel={() => {
this.setState({
modifyRecordVisible: false,
});
}}
onComplete={() => {
this.setState({
modifyRecordVisible: false,
});
}}
/>
</ModulaCard>
);
}
}

24
web/src/DataTable.less Normal file
View File

@ -0,0 +1,24 @@
[data-component=data-table-box]{
width: 100%;
height: calc(100% - .38rem);
box-sizing: border-box;
padding: 0rem .25rem 0rem .4rem;
position: relative;
.ant-table-thead,.ant-table-row{
height: .58rem;
}
.ant-table-cell{
font-size: .14rem;
}
> .footer{
width: 100%;
height: .40rem;
display: flex;
align-items: center;
position: absolute;
bottom: 0;
left: 0;
box-sizing: border-box;
padding-left: .4rem;
}
}

245
web/src/DatasetEditPage.js Normal file
View File

@ -0,0 +1,245 @@
import React from "react";
import {Button, Card, Col, DatePicker, Input, Row, Select} from "antd";
import * as DatasetBackend from "./backend/DatasetBackend";
import * as Setting from "./Setting";
import moment from "moment";
import i18next from "i18next";
const {Option} = Select;
class DatasetEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
datasetName: props.match.params.datasetName,
dataset: null,
};
}
UNSAFE_componentWillMount() {
this.getDataset();
}
getDataset() {
DatasetBackend.getDataset(this.props.account.name, this.state.datasetName)
.then((dataset) => {
this.setState({
dataset: dataset,
});
});
}
parseDatasetField(key, value) {
if (["score"].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateDatasetField(key, value) {
value = this.parseDatasetField(key, value);
let dataset = this.state.dataset;
dataset[key] = value;
this.setState({
dataset: dataset,
});
}
renderDataset() {
return (
<Card size="small" title={
<div>
{i18next.t("dataset:Edit Dataset")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitDatasetEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div>
} style={{marginLeft: "5px"}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("general:Name")}:
</Col>
<Col span={22} >
<Input value={this.state.dataset.name} onChange={e => {
this.updateDatasetField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Start date")}:
</Col>
<Col span={5} >
<DatePicker defaultValue={moment(this.state.dataset.startDate, "YYYY-MM-DD")} onChange={(time, timeString) => {
this.updateDatasetField("startDate", timeString);
}} />
</Col>
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:End date")}:
</Col>
<Col span={10} >
<DatePicker defaultValue={moment(this.state.dataset.endDate, "YYYY-MM-DD")} onChange={(time, timeString) => {
this.updateDatasetField("endDate", timeString);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Full name")}:
</Col>
<Col span={22} >
<Input value={this.state.dataset.fullName} onChange={e => {
this.updateDatasetField("fullName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Organizer")}:
</Col>
<Col span={22} >
<Input value={this.state.dataset.organizer} onChange={e => {
this.updateDatasetField("organizer", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Location")}:
</Col>
<Col span={22} >
<Input value={this.state.dataset.location} onChange={e => {
this.updateDatasetField("location", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Address")}:
</Col>
<Col span={22} >
<Input value={this.state.dataset.address} onChange={e => {
this.updateDatasetField("address", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("general:Status")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.dataset.status} onChange={(value => {this.updateDatasetField("status", value);})}>
{
[
{id: "Public", name: "Public (Everyone can see it)"},
{id: "Hidden", name: "Hidden (Only yourself can see it)"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Carousels")}:
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} placeholder="Please input"
value={this.state.dataset.carousels}
onChange={value => {
this.updateDatasetField("carousels", value);
}}
>
{
this.state.dataset.carousels.map((carousel, index) => <Option key={carousel}>{carousel}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Introduction text")}:
</Col>
<Col span={22} >
<Input value={this.state.dataset.introText} onChange={e => {
this.updateDatasetField("introText", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Default item")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.dataset.defaultItem} onChange={value => {this.updateDatasetField("defaultItem", value);}}>
{
this.state.dataset.treeItems.filter(treeItem => treeItem.children.length === 0).map((treeItem, index) => <Option key={treeItem.title}>{`${treeItem.title} | ${treeItem.titleEn}`}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{i18next.t("dataset:Language")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.dataset.language} onChange={(value => {this.updateDatasetField("language", value);})}>
{
[
{id: "zh", name: "zh"},
{id: "en", name: "en"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
</Card>
);
}
submitDatasetEdit() {
let dataset = Setting.deepCopy(this.state.dataset);
DatasetBackend.updateDataset(this.state.dataset.owner, this.state.datasetName, dataset)
.then((res) => {
if (res) {
Setting.showMessage("success", "Successfully saved");
this.setState({
datasetName: this.state.dataset.name,
});
this.props.history.push(`/datasets/${this.state.dataset.name}`);
} else {
Setting.showMessage("error", "failed to save: server side failure");
this.updateDatasetField("name", this.state.datasetName);
}
})
.catch(error => {
Setting.showMessage("error", `failed to save: ${error}`);
});
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.state.dataset !== null ? this.renderDataset() : null
}
</Col>
<Col span={1}>
</Col>
</Row>
<Row style={{margin: 10}}>
<Col span={2}>
</Col>
<Col span={18}>
<Button type="primary" size="large" onClick={this.submitDatasetEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col>
</Row>
</div>
);
}
}
export default DatasetEditPage;

208
web/src/DatasetListPage.js Normal file
View File

@ -0,0 +1,208 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as DatasetBackend from "./backend/DatasetBackend";
import i18next from "i18next";
class DatasetListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
datasets: null,
};
}
UNSAFE_componentWillMount() {
this.getDatasets();
}
getDatasets() {
DatasetBackend.getDatasets(this.props.account.name)
.then((res) => {
this.setState({
datasets: res,
});
});
}
newDataset() {
return {
owner: this.props.account.name,
name: `dataset_${this.state.datasets.length}`,
createdTime: moment().format(),
startDate: moment().format("YYYY-MM-DD"),
endDate: moment().format("YYYY-MM-DD"),
fullName: `Dataset ${this.state.datasets.length}`,
organizer: "Casbin",
location: "Shanghai, China",
address: "3663 Zhongshan Road North",
status: "Public",
language: "zh",
carousels: [],
introText: "Introduction..",
defaultItem: "Home",
treeItems: [{key: "Home", title: "首页", titleEn: "Home", content: "内容", contentEn: "Content", children: []}],
};
}
addDataset() {
const newDataset = this.newDataset();
DatasetBackend.addDataset(newDataset)
.then((res) => {
Setting.showMessage("success", "Dataset added successfully");
this.setState({
datasets: Setting.prependRow(this.state.datasets, newDataset),
});
}
)
.catch(error => {
Setting.showMessage("error", `Dataset failed to add: ${error}`);
});
}
deleteDataset(i) {
DatasetBackend.deleteDataset(this.state.datasets[i])
.then((res) => {
Setting.showMessage("success", "Dataset deleted successfully");
this.setState({
datasets: Setting.deleteRow(this.state.datasets, i),
});
}
)
.catch(error => {
Setting.showMessage("error", `Dataset failed to delete: ${error}`);
});
}
renderTable(datasets) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "120px",
sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => {
return (
<Link to={`/datasets/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("dataset:Start date"),
dataIndex: "startDate",
key: "startDate",
width: "70px",
sorter: (a, b) => a.startDate.localeCompare(b.startDate),
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("dataset:End date"),
dataIndex: "endDate",
key: "endDate",
width: "70px",
sorter: (a, b) => a.endDate.localeCompare(b.endDate),
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("dataset:Full name"),
dataIndex: "fullName",
key: "fullName",
width: "200px",
sorter: (a, b) => a.fullName.localeCompare(b.fullName),
},
{
title: i18next.t("dataset:Organizer"),
dataIndex: "organizer",
key: "organizer",
width: "120px",
sorter: (a, b) => a.organizer.localeCompare(b.organizer),
},
{
title: i18next.t("dataset:Location"),
dataIndex: "location",
key: "location",
width: "120px",
sorter: (a, b) => a.location.localeCompare(b.location),
},
{
title: i18next.t("dataset:Address"),
dataIndex: "address",
key: "address",
width: "120px",
sorter: (a, b) => a.address.localeCompare(b.address),
},
{
title: i18next.t("general:Status"),
dataIndex: "status",
key: "status",
width: "80px",
sorter: (a, b) => a.status.localeCompare(b.status),
},
{
title: i18next.t("general:Action"),
dataIndex: "action",
key: "action",
width: "120px",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/datasets/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete dataset: ${record.name} ?`}
onConfirm={() => this.deleteDataset(index)}
okText="OK"
cancelText="Cancel"
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);
},
},
];
return (
<div>
<Table columns={columns} dataSource={datasets} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{i18next.t("general:Datasets")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addDataset.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={datasets === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.datasets)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default DatasetListPage;

View File

@ -0,0 +1,59 @@
import React, {Component} from "react";
import {Button, Col, Row} from "antd";
import "./HistoryQuestion.less";
export default class HistoryQuestion extends Component {
render() {
return (
<div className="history-question-item" data-component="history-question-item">
<Row gutter={[16, 16]} className="queston-content">
<Col span="16" className="question-title">
<span>题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目......题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目.题目题目题目题目题目题目题目题目.(最多5行过多省略)题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目......题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目题目.题目题目题目题目题目题目题目题目.(最多5行过多省略)</span>
</Col>
<Col span="8" className="question-params">
<Row className="params-item">
<Col span="12" className="title">
<span>学科名称</span>
</Col>
<Col span="12" className="value" >
<span>数学</span>
</Col>
</Row>
<Row className="params-item">
<Col span="12" className="title">
<span>能力纬度</span>
</Col>
<Col span="12" className="value">
<span>参数1参数2参数3</span>
</Col>
</Row>
<Row className="params-item">
<Col span="12" className="title">
<span>内容纬度</span>
</Col>
<Col span="12" className="value">
<span>参数1参数2参数3</span>
</Col>
</Row>
<Row className="params-item">
<Col span="12" className="title">
<span>难度等级</span>
</Col>
<Col span="12" className="value">
<span>参数1</span>
</Col>
</Row>
</Col>
</Row>
<Row gutter={[16, 16]} className="question-footer">
<Col span="16" className="question-answer">
<span>答案xxxx</span>
</Col>
<Col span="8">
<Button type="primary" size="small" style={{backgroundColor: "#56BFFF", borderColor: "#56BFFF"}}>使用</Button>
</Col>
</Row>
</div>
);
}
}

View File

@ -0,0 +1,63 @@
[data-component=history-question-item]{
width: 100%;
box-sizing: border-box;
padding: .14rem .1rem .16rem .24rem;
background-color: #FFFEFE33;
border: .01rem solid #2DB7F5;
margin-top: .2rem;
> .queston-content{
width: 100%;
> .question-title{
width: 100%;
box-sizing: border-box;
padding-top: .11rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
text-align: justify;
line-height: .22rem;
> span{
font-size: .14rem;
color: #00000059;
}
}
> .question-params{
width: 100%;
> .params-item{
width: 100%;
> .title{
line-height: .22rem;
> span{
font-size: .1rem;
color: #1890FF;
}
}
> .value{
line-height: .22rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
> span{
font-size: .1rem;
color: #1890FF;
}
}
}
}
}
> .question-footer{
width: 100%;
margin-top: .25rem;
> .question-answer{
display: flex;
align-items: center;
> span{
font-size: .1rem;
color: #1890FF;
}
}
}
}

20
web/src/HomePage.js Normal file
View File

@ -0,0 +1,20 @@
import React from "react";
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
};
}
render() {
return (
<div>
hello
</div>
);
}
}
export default HomePage;