From c263a62514c1b0d34a27a1c2a044c98f7c89ca3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E6=A0=91=E6=98=8E?= <775174143@qq.com> Date: Fri, 2 Aug 2019 21:17:09 +0800 Subject: [PATCH 1/2] b --- .../src/modules/courses/busyWork/common/WorkDetailPageHeader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js b/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js index eef99bfcd..56460a44a 100644 --- a/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js +++ b/public/react/src/modules/courses/busyWork/common/WorkDetailPageHeader.js @@ -90,7 +90,7 @@ class WorkDetailPageHeader extends Component{ } `}</style> <CBreadcrumb items={[ - { to: current_user.first_category_url, name: course_name}, + { to: current_user&¤t_user.first_category_url, name: course_name}, { to: `/courses/${courseId}/${moduleEngName}/${category_id}`, name: category_name }, window.location.pathname.indexOf('appraise') == -1 ? { } : { to: `/courses/${courseId}/${moduleEngName}/${workId}/list`, name: '作业详情' }, // 1. 与上一条联动,当匿评他人作品时,TA人作品的作者真实姓名切换为“匿名” From 55b6c641593b02dd701f698416bcbe772377e75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E6=A0=91=E6=98=8E?= <775174143@qq.com> Date: Fri, 2 Aug 2019 21:46:17 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/courses/boards/BoardsNew.js | 896 +++++----- .../src/modules/courses/boards/TopicDetail.js | 1452 ++++++++--------- .../courses/busyWork/CommonWorkDetailIndex.js | 2 +- .../modules/courses/exercise/ExerciseNew.js | 1140 ++++++------- .../graduation/topics/GraduateTopicDetail.js | 2 +- .../graduation/topics/GraduateTopicNew.js | 2 +- 6 files changed, 1747 insertions(+), 1747 deletions(-) diff --git a/public/react/src/modules/courses/boards/BoardsNew.js b/public/react/src/modules/courses/boards/BoardsNew.js index 56dddff7c..cea4f47aa 100644 --- a/public/react/src/modules/courses/boards/BoardsNew.js +++ b/public/react/src/modules/courses/boards/BoardsNew.js @@ -1,449 +1,449 @@ -import React,{ Component } from "react"; - -import { - Form, Input, InputNumber, Switch, Radio, - Slider, Button, Upload, Icon, Rate, Checkbox, message, - Row, Col, Select, Modal, Divider -} from 'antd'; -import TPMMDEditor from '../../tpm/challengesnew/TPMMDEditor'; -import axios from 'axios' -import './board.css' -import "../common/formCommon.css" -import AddDirModal from './AddDirModal' -import { RouteHOC } from './common.js' -import CBreadcrumb from '../common/CBreadcrumb' -import {getUploadActionUrl, bytesToSize, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll} from 'educoder'; - -const confirm = Modal.confirm; -const $ = window.$ -const { Option } = Select; -// https://lanhuapp.com/web/#/item/project/board/detail?pid=a3bcd4b1-99ce-4e43-8ead-5b8b0a410807&project_id=a3bcd4b1-99ce-4e43-8ead-5b8b0a410807&image_id=71072679-b925-4824-aceb-4649535e3652 -class BoardsNew extends Component{ - constructor(props){ - super(props); - - this.mdRef = React.createRef(); - - this.state = { - fileList: [], - boards: [], - title_num: 60 - } - } - addSuccess = () => { - this.fetchBoards() - } - fetchBoards = () => { - const isEdit = this.isEdit - const boardId = this.props.match.params.boardId - - const boardsUrl = `/courses/board_list.json?board_id=${boardId}` - axios.get(boardsUrl, { }) - .then((response) => { - if (response.data.status == 0) { - this.setState({ - boards: response.data.data.boards || [], - course_id: response.data.data.course_id - }) - if (!isEdit) { - response.data.data.boards.forEach( board => { - if (board.id == boardId) { - this.setState({ board_name: board.name }) - } - }) - // board_name - } - } - }) - .catch(function (error) { - console.log(error); - }); - } - componentDidMount = () => { - - const topicId = this.props.match.params.topicId - const isEdit = !!topicId - this.isEdit = isEdit - - const boardId = this.props.match.params.boardId - - this.fetchBoards() - - if (isEdit) { - const url = `/messages/${topicId}.json` - axios.get(url, { - }) - .then((response) => { - if (response.data.status == 0) { - const { id, data } = response.data; - if (data) { - this.editTopic = data; - this.props.form.setFieldsValue({ - sticky: !!data.sticky, - content: data.content, - subject: data.subject, - select_board_id: data.board_id // TODO 没返回给前端 - }); - this.mdRef.current.setValue(data.content) - const _fileList = data.attachments.map(item => { - return { - id: item.id, - uid: item.id, - name: appendFileSizeToUploadFile(item), - url: item.url, - status: 'done' - } - }) - - this.setState({ fileList: _fileList, board_name: data.board_name, title_num: 60 - parseInt(data.subject.length) }) - } - } - }) - .catch(function (error) { - console.log(error); - }); - } else { - const boardId = this.props.match.params.boardId - - this.props.form.setFieldsValue({ - select_board_id: parseInt(boardId) - }); - } - } - handleSubmit = (e) => { - e.preventDefault(); - const cid = this.state.course_id - const boardId = this.props.match.params.boardId - - this.props.form.validateFieldsAndScroll((err, values) => { - if (!err) { - console.log('Received values of form: ', values); - if (this.isEdit == true) { - const editTopic = this.editTopic - const editUrl = `/messages/${editTopic.id}.json` - - let attachment_ids = undefined - if (this.state.fileList) { - attachment_ids = this.state.fileList.map(item => { - return item.response ? item.response.id : item.id - }) - } - axios.put(editUrl, { - subject: values.subject, - select_board_id: values.select_board_id, - content: values.content, - sticky: values.sticky, - attachment_ids, - }) - .then((response) => { - if (response.data.status == 0) { - const { id } = response.data; - console.log('--- success') - - this.props.toDetailPage(cid, values.select_board_id, editTopic.id) - } - }) - .catch(function (error) { - console.log(error); - }); - } else { - const url = `/boards/${boardId}/messages.json` - let attachment_ids = undefined - if (this.state.fileList) { - attachment_ids = this.state.fileList.map(item => { - return item.response.id - }) - } - - axios.post(url, { - ...values, - course_id: cid, - attachment_ids, - }) - .then((response) => { - if (response.data.data && response.data.status == 0) { - const { id } = response.data.data; - if (id) { - console.log('--- success') - this.props.toDetailPage(cid, values.select_board_id, id) - } - } - }) - .catch(function (error) { - console.log(error); - }); - } - } else { - $("html").animate({ scrollTop: $('html').scrollTop() - 100 }) - } - }); - } - // 附件相关 START - handleChange = (info) => { - let fileList = info.fileList; - this.setState({ fileList: appendFileSizeToUploadFileAll(fileList) - }); - } - onAttachmentRemove = (file) => { - confirm({ - // title: '确定要删除这个附件吗?', - title: '是否确认删除?', - - okText: '确定', - cancelText: '取消', - // content: 'Some descriptions', - onOk: () => { - this.deleteAttachment(file) - }, - onCancel() { - console.log('Cancel'); - }, - }); - - - return false; - } - deleteAttachment = (file) => { - // 初次上传不能直接取uid - const url = `/attachments/${file.response ? file.response.id : file.uid}.json` - axios.delete(url, { - }) - .then((response) => { - if (response.data) { - const { status } = response.data; - if (status == 0) { - console.log('--- success') - - this.setState((state) => { - const index = state.fileList.indexOf(file); - const newFileList = state.fileList.slice(); - newFileList.splice(index, 1); - return { - fileList: newFileList, - }; - }); - } - } - }) - .catch(function (error) { - console.log(error); - }); - } - // 附件相关 ------------ END - changeTitle=(e)=>{ - console.log(e.target.value.length); - this.setState({ - title_num: 60 - parseInt(e.target.value.length) - }) - } - render() { - let { addGroup, fileList, course_id, title_num } = this.state; - const { getFieldDecorator } = this.props.form; - const { current_user } = this.props - - const formItemLayout = { - labelCol: { - xs: { span: 24 }, - // sm: { span: 8 }, - sm: { span: 24 }, - }, - wrapperCol: { - xs: { span: 24 }, - // sm: { span: 16 }, - sm: { span: 24 }, - }, - }; - - const uploadProps = { - width: 600, - fileList, - multiple: true, - // https://github.com/ant-design/ant-design/issues/15505 - // showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。 - // showUploadList: false, - action: `${getUploadActionUrl()}`, - onChange: this.handleChange, - onRemove: this.onAttachmentRemove, - beforeUpload: (file) => { - console.log('beforeUpload', file.name); - const isLt150M = file.size / 1024 / 1024 < 150; - if (!isLt150M) { - message.error('文件大小必须小于150MB!'); - } - return isLt150M; - }, - }; - const isAdmin = this.props.isAdmin() - const courseId=this.props.match.params.coursesId; - const boardId = this.props.match.params.boardId - - return( - <div className="newMain "> - <AddDirModal {...this.props} - title="添加目录" - label="目录名称" - ref="addDirModal" - addSuccess={this.addSuccess} - ></AddDirModal> - <style>{` - .courseForm .ant-form { - } - .courseForm .formBlock { - padding: 20px 30px 30px 30px; - border-bottom: 1px solid #EDEDED; - margin-bottom: 0px; - background: #fff; - } - .courseForm .noBorder { - border-bottom: none; - } - - `}</style> - <div className="edu-class-container edu-position courseForm"> - <CBreadcrumb items={[ - { to: current_user.first_category_url, name: this.props.coursedata ? this.props.coursedata.name : ''}, - { to: `/courses/${courseId}/boards/${boardId}`, name: this.state.board_name }, - { name: this.isEdit ? '帖子编辑' : '帖子新建'} - ]}></CBreadcrumb> - - <p className="clearfix mt20 mb20"> - <span className="fl font-24 color-grey-3">{this.isEdit ? "编辑" : "新建"}帖子</span> - <a href="javascript:void(0)" className="color-grey-6 fr font-16 mr2" - onClick={() => this.props.history.goBack()}> - 返回 - </a> - </p> - {/* notRequired */} - <Form {...formItemLayout} onSubmit={this.handleSubmit}> - <div className="formBlock" style={{paddingBottom: '0px', position: 'relative'}}> - { isAdmin && - <React.Fragment> - {getFieldDecorator('sticky', { - valuePropName: 'checked', - })( - isAdmin && <Checkbox style={{ right: '22px', - top: '28px', - position: 'absolute' - }}>置顶</Checkbox> - )} - {/* checkbox 有个边距样式 .ant-checkbox-wrapper + span, */} - <span style={{ "padding-left": 0, "padding-right": 0 }}></span> - </React.Fragment> - } - <Form.Item - label="标题" - className="topicTitle " - > - - {getFieldDecorator('subject', { - rules: [{ - required: true, message: '请输入标题', - }, { - max: 60, message: '最大限制为60个字符', - }], - })( - <Input placeholder="请输入帖子标题,最大限制60个字符" className="searchViewAfter" maxLength="60" - onInput={this.changeTitle} addonAfter={String(title_num)} /> - )} - </Form.Item> - - <Form.Item - label="" - style={{ display: 'inline-block' }} - > - {getFieldDecorator('select_board_id', { - // initialValue: '3779', - })( - <Select style={{ width: 230 }} - dropdownRender={menu => ( - <div> - {menu} - <Divider style={{ margin: '4px 0' }} /> - <div style={{ padding: '8px', cursor: 'pointer' }} onMouseDown={() => this.refs['addDirModal'].open()}> - <Icon type="plus" /> 添加目录 - </div> - </div> - )} - > - {this.state.boards.map(item => { - return ( - <Option value={item.id}>{item.name}</Option> - ) - })} - </Select> - )} - </Form.Item> - - {/* { isAdmin && <Form.Item - label="" - style={{ display: 'inline-block', marginLeft: "14px" }} - > - {getFieldDecorator('sticky', { - })( - <Checkbox>置顶</Checkbox> - )} - </Form.Item> } */} - </div> - - <style>{` - .courseMessageMD { - width: 1140px; - } - - .uploadBtn.ant-btn { - border: none; - color: #4CACFF; - box-shadow: none; - background: transparent; - padding: 0 6px; - } - .upload_1 .ant-upload-list { - width: 350px; - } - - `}</style> - - <div className="formBlock noBorder"> - - <Form.Item - label="内容" - className="mdInForm" - > - {getFieldDecorator('content', { - rules: [{ - required: true, message: '请输入帖子内容', - }, { - max: 10000, message: '最大限制为10000个字符', - }], - })( - <TPMMDEditor ref={this.mdRef} placeholder={'请在此输入帖子详情,最大限制为10000个字符'} - mdID={'courseMessageMD'} initValue={this.editTopic ? this.editTopic.content : ''} className="courseMessageMD"></TPMMDEditor> - )} - </Form.Item> - - <Upload {...uploadProps} className="upload_1"> - <Button className="uploadBtn"> - <Icon type="upload" /> 上传附件 - </Button> - (单个文件150M以内) - </Upload> - </div> - - <Form.Item> - <div className="clearfix mt30 mb30"> - <Button type="primary" htmlType="submit" className="defalutSubmitbtn fl mr20">提交</Button> - <a className="defalutCancelbtn fl" - onClick={() => this.isEdit ? - this.props.toDetailPage(Object.assign({}, this.props.match.params, {'coursesId': course_id})) : - this.props.toListPage(Object.assign({}, this.props.match.params, {'coursesId': course_id})) }>取消</ a> - </div> - </Form.Item> - </Form> - </div> - </div> - ) - } -} - -const WrappedBoardsNew = Form.create({ name: 'boardsNew' })(BoardsNew); +import React,{ Component } from "react"; + +import { + Form, Input, InputNumber, Switch, Radio, + Slider, Button, Upload, Icon, Rate, Checkbox, message, + Row, Col, Select, Modal, Divider +} from 'antd'; +import TPMMDEditor from '../../tpm/challengesnew/TPMMDEditor'; +import axios from 'axios' +import './board.css' +import "../common/formCommon.css" +import AddDirModal from './AddDirModal' +import { RouteHOC } from './common.js' +import CBreadcrumb from '../common/CBreadcrumb' +import {getUploadActionUrl, bytesToSize, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll} from 'educoder'; + +const confirm = Modal.confirm; +const $ = window.$ +const { Option } = Select; +// https://lanhuapp.com/web/#/item/project/board/detail?pid=a3bcd4b1-99ce-4e43-8ead-5b8b0a410807&project_id=a3bcd4b1-99ce-4e43-8ead-5b8b0a410807&image_id=71072679-b925-4824-aceb-4649535e3652 +class BoardsNew extends Component{ + constructor(props){ + super(props); + + this.mdRef = React.createRef(); + + this.state = { + fileList: [], + boards: [], + title_num: 60 + } + } + addSuccess = () => { + this.fetchBoards() + } + fetchBoards = () => { + const isEdit = this.isEdit + const boardId = this.props.match.params.boardId + + const boardsUrl = `/courses/board_list.json?board_id=${boardId}` + axios.get(boardsUrl, { }) + .then((response) => { + if (response.data.status == 0) { + this.setState({ + boards: response.data.data.boards || [], + course_id: response.data.data.course_id + }) + if (!isEdit) { + response.data.data.boards.forEach( board => { + if (board.id == boardId) { + this.setState({ board_name: board.name }) + } + }) + // board_name + } + } + }) + .catch(function (error) { + console.log(error); + }); + } + componentDidMount = () => { + + const topicId = this.props.match.params.topicId + const isEdit = !!topicId + this.isEdit = isEdit + + const boardId = this.props.match.params.boardId + + this.fetchBoards() + + if (isEdit) { + const url = `/messages/${topicId}.json` + axios.get(url, { + }) + .then((response) => { + if (response.data.status == 0) { + const { id, data } = response.data; + if (data) { + this.editTopic = data; + this.props.form.setFieldsValue({ + sticky: !!data.sticky, + content: data.content, + subject: data.subject, + select_board_id: data.board_id // TODO 没返回给前端 + }); + this.mdRef.current.setValue(data.content) + const _fileList = data.attachments.map(item => { + return { + id: item.id, + uid: item.id, + name: appendFileSizeToUploadFile(item), + url: item.url, + status: 'done' + } + }) + + this.setState({ fileList: _fileList, board_name: data.board_name, title_num: 60 - parseInt(data.subject.length) }) + } + } + }) + .catch(function (error) { + console.log(error); + }); + } else { + const boardId = this.props.match.params.boardId + + this.props.form.setFieldsValue({ + select_board_id: parseInt(boardId) + }); + } + } + handleSubmit = (e) => { + e.preventDefault(); + const cid = this.state.course_id + const boardId = this.props.match.params.boardId + + this.props.form.validateFieldsAndScroll((err, values) => { + if (!err) { + console.log('Received values of form: ', values); + if (this.isEdit == true) { + const editTopic = this.editTopic + const editUrl = `/messages/${editTopic.id}.json` + + let attachment_ids = undefined + if (this.state.fileList) { + attachment_ids = this.state.fileList.map(item => { + return item.response ? item.response.id : item.id + }) + } + axios.put(editUrl, { + subject: values.subject, + select_board_id: values.select_board_id, + content: values.content, + sticky: values.sticky, + attachment_ids, + }) + .then((response) => { + if (response.data.status == 0) { + const { id } = response.data; + console.log('--- success') + + this.props.toDetailPage(cid, values.select_board_id, editTopic.id) + } + }) + .catch(function (error) { + console.log(error); + }); + } else { + const url = `/boards/${boardId}/messages.json` + let attachment_ids = undefined + if (this.state.fileList) { + attachment_ids = this.state.fileList.map(item => { + return item.response.id + }) + } + + axios.post(url, { + ...values, + course_id: cid, + attachment_ids, + }) + .then((response) => { + if (response.data.data && response.data.status == 0) { + const { id } = response.data.data; + if (id) { + console.log('--- success') + this.props.toDetailPage(cid, values.select_board_id, id) + } + } + }) + .catch(function (error) { + console.log(error); + }); + } + } else { + $("html").animate({ scrollTop: $('html').scrollTop() - 100 }) + } + }); + } + // 附件相关 START + handleChange = (info) => { + let fileList = info.fileList; + this.setState({ fileList: appendFileSizeToUploadFileAll(fileList) + }); + } + onAttachmentRemove = (file) => { + confirm({ + // title: '确定要删除这个附件吗?', + title: '是否确认删除?', + + okText: '确定', + cancelText: '取消', + // content: 'Some descriptions', + onOk: () => { + this.deleteAttachment(file) + }, + onCancel() { + console.log('Cancel'); + }, + }); + + + return false; + } + deleteAttachment = (file) => { + // 初次上传不能直接取uid + const url = `/attachments/${file.response ? file.response.id : file.uid}.json` + axios.delete(url, { + }) + .then((response) => { + if (response.data) { + const { status } = response.data; + if (status == 0) { + console.log('--- success') + + this.setState((state) => { + const index = state.fileList.indexOf(file); + const newFileList = state.fileList.slice(); + newFileList.splice(index, 1); + return { + fileList: newFileList, + }; + }); + } + } + }) + .catch(function (error) { + console.log(error); + }); + } + // 附件相关 ------------ END + changeTitle=(e)=>{ + console.log(e.target.value.length); + this.setState({ + title_num: 60 - parseInt(e.target.value.length) + }) + } + render() { + let { addGroup, fileList, course_id, title_num } = this.state; + const { getFieldDecorator } = this.props.form; + const { current_user } = this.props + + const formItemLayout = { + labelCol: { + xs: { span: 24 }, + // sm: { span: 8 }, + sm: { span: 24 }, + }, + wrapperCol: { + xs: { span: 24 }, + // sm: { span: 16 }, + sm: { span: 24 }, + }, + }; + + const uploadProps = { + width: 600, + fileList, + multiple: true, + // https://github.com/ant-design/ant-design/issues/15505 + // showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。 + // showUploadList: false, + action: `${getUploadActionUrl()}`, + onChange: this.handleChange, + onRemove: this.onAttachmentRemove, + beforeUpload: (file) => { + console.log('beforeUpload', file.name); + const isLt150M = file.size / 1024 / 1024 < 150; + if (!isLt150M) { + message.error('文件大小必须小于150MB!'); + } + return isLt150M; + }, + }; + const isAdmin = this.props.isAdmin() + const courseId=this.props.match.params.coursesId; + const boardId = this.props.match.params.boardId + + return( + <div className="newMain "> + <AddDirModal {...this.props} + title="添加目录" + label="目录名称" + ref="addDirModal" + addSuccess={this.addSuccess} + ></AddDirModal> + <style>{` + .courseForm .ant-form { + } + .courseForm .formBlock { + padding: 20px 30px 30px 30px; + border-bottom: 1px solid #EDEDED; + margin-bottom: 0px; + background: #fff; + } + .courseForm .noBorder { + border-bottom: none; + } + + `}</style> + <div className="edu-class-container edu-position courseForm"> + <CBreadcrumb items={[ + { to: current_user&¤t_user.first_category_url, name: this.props.coursedata ? this.props.coursedata.name : ''}, + { to: `/courses/${courseId}/boards/${boardId}`, name: this.state.board_name }, + { name: this.isEdit ? '帖子编辑' : '帖子新建'} + ]}></CBreadcrumb> + + <p className="clearfix mt20 mb20"> + <span className="fl font-24 color-grey-3">{this.isEdit ? "编辑" : "新建"}帖子</span> + <a href="javascript:void(0)" className="color-grey-6 fr font-16 mr2" + onClick={() => this.props.history.goBack()}> + 返回 + </a> + </p> + {/* notRequired */} + <Form {...formItemLayout} onSubmit={this.handleSubmit}> + <div className="formBlock" style={{paddingBottom: '0px', position: 'relative'}}> + { isAdmin && + <React.Fragment> + {getFieldDecorator('sticky', { + valuePropName: 'checked', + })( + isAdmin && <Checkbox style={{ right: '22px', + top: '28px', + position: 'absolute' + }}>置顶</Checkbox> + )} + {/* checkbox 有个边距样式 .ant-checkbox-wrapper + span, */} + <span style={{ "padding-left": 0, "padding-right": 0 }}></span> + </React.Fragment> + } + <Form.Item + label="标题" + className="topicTitle " + > + + {getFieldDecorator('subject', { + rules: [{ + required: true, message: '请输入标题', + }, { + max: 60, message: '最大限制为60个字符', + }], + })( + <Input placeholder="请输入帖子标题,最大限制60个字符" className="searchViewAfter" maxLength="60" + onInput={this.changeTitle} addonAfter={String(title_num)} /> + )} + </Form.Item> + + <Form.Item + label="" + style={{ display: 'inline-block' }} + > + {getFieldDecorator('select_board_id', { + // initialValue: '3779', + })( + <Select style={{ width: 230 }} + dropdownRender={menu => ( + <div> + {menu} + <Divider style={{ margin: '4px 0' }} /> + <div style={{ padding: '8px', cursor: 'pointer' }} onMouseDown={() => this.refs['addDirModal'].open()}> + <Icon type="plus" /> 添加目录 + </div> + </div> + )} + > + {this.state.boards.map(item => { + return ( + <Option value={item.id}>{item.name}</Option> + ) + })} + </Select> + )} + </Form.Item> + + {/* { isAdmin && <Form.Item + label="" + style={{ display: 'inline-block', marginLeft: "14px" }} + > + {getFieldDecorator('sticky', { + })( + <Checkbox>置顶</Checkbox> + )} + </Form.Item> } */} + </div> + + <style>{` + .courseMessageMD { + width: 1140px; + } + + .uploadBtn.ant-btn { + border: none; + color: #4CACFF; + box-shadow: none; + background: transparent; + padding: 0 6px; + } + .upload_1 .ant-upload-list { + width: 350px; + } + + `}</style> + + <div className="formBlock noBorder"> + + <Form.Item + label="内容" + className="mdInForm" + > + {getFieldDecorator('content', { + rules: [{ + required: true, message: '请输入帖子内容', + }, { + max: 10000, message: '最大限制为10000个字符', + }], + })( + <TPMMDEditor ref={this.mdRef} placeholder={'请在此输入帖子详情,最大限制为10000个字符'} + mdID={'courseMessageMD'} initValue={this.editTopic ? this.editTopic.content : ''} className="courseMessageMD"></TPMMDEditor> + )} + </Form.Item> + + <Upload {...uploadProps} className="upload_1"> + <Button className="uploadBtn"> + <Icon type="upload" /> 上传附件 + </Button> + (单个文件150M以内) + </Upload> + </div> + + <Form.Item> + <div className="clearfix mt30 mb30"> + <Button type="primary" htmlType="submit" className="defalutSubmitbtn fl mr20">提交</Button> + <a className="defalutCancelbtn fl" + onClick={() => this.isEdit ? + this.props.toDetailPage(Object.assign({}, this.props.match.params, {'coursesId': course_id})) : + this.props.toListPage(Object.assign({}, this.props.match.params, {'coursesId': course_id})) }>取消</ a> + </div> + </Form.Item> + </Form> + </div> + </div> + ) + } +} + +const WrappedBoardsNew = Form.create({ name: 'boardsNew' })(BoardsNew); export default RouteHOC()(WrappedBoardsNew); \ No newline at end of file diff --git a/public/react/src/modules/courses/boards/TopicDetail.js b/public/react/src/modules/courses/boards/TopicDetail.js index 7aaecdd16..970cf798a 100644 --- a/public/react/src/modules/courses/boards/TopicDetail.js +++ b/public/react/src/modules/courses/boards/TopicDetail.js @@ -1,726 +1,726 @@ -import React, { Component } from 'react'; -import { Redirect } from 'react-router'; - -import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom"; - -import PropTypes from 'prop-types'; - -import classNames from 'classnames' - -import axios from 'axios' - -import moment from 'moment' - -import Comments from '../../comment/Comments' - -import update from 'immutability-helper' -import RewardDialog from '../../common/RewardDialog'; -import {ImageLayerOfCommentHOC} from '../../page/layers/ImageLayerOfCommentHOC' - -import MemoDetailMDEditor from '../../forums/MemoDetailMDEditor' -import { RouteHOC } from './common.js' -import '../../forums/Post.css' -import '../../forums/RightSection.css' -import './TopicDetail.css' -import '../common/courseMessage.css' -import { Pagination, Tooltip } from 'antd' -import { bytesToSize, ConditionToolTip, markdownToHTML, MarkdownToHtml } from 'educoder' -import SendToCourseModal from '../coursesPublic/modal/SendToCourseModal' -import CBreadcrumb from '../common/CBreadcrumb' -import { generateComments, generateChildComments, _findById, handleContentBeforeCreateNew, addNewComment - , addSecondLevelComment, NEED_TO_WRITE_CONTENT, handleContentBeforeCreateSecondLevelComment - , handleDeleteComment, handleCommentPraise, handleHiddenComment } from '../common/CommentsHelper' - -const $ = window.$ -const REPLY_PAGE_COUNT = 10 -function urlStringify(params) { - let noParams = true; - let paramsUrl = ''; - for (let key in params) { - noParams = false; - paramsUrl += `${key}=${params[key]}&` - } - if (noParams) { - return ''; - } - paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1); - return paramsUrl; -} -class TopicDetail extends Component { - constructor(props) { - super(props) - - this.state = { - memo: {}, - memoLoading: true, - hasMoreComments: false, - pageCount: 1, - comments: [], - goldRewardDialogOpen: false, - } - } - componentDidMount() { - window.$("html,body").animate({"scrollTop":0}) - - const topicId = this.props.match.params.topicId - const bid = this.props.match.params.boardId - - const memoUrl = `/messages/${topicId}.json`; - this.setState({ - memoLoading: true - }) - axios.get(memoUrl,{ - }) - .then((response) => { - - if (response.data.status === -1) { - setTimeout(() => { - this.props.showNotification('帖子不存在!') - }, 300) - // this.props.toListPage(response.data.data.course_id, bid) - return; - } else { - - this.setState({ - memo: Object.assign({}, { - ...response.data.data, - replies_count: response.data.data.total_replies_count - }, {...this.state.memo}) - }, () => { - }) - - // const { memo_replies, memo } = response.data; - // let hasMoreComments = false; - // if (memo_replies && memo_replies.length === 10 && memo.total_replies_count > 10) { - // // 遍历一遍,计算下是否还有评论未加载 - // let totalCount = 10; - // memo_replies.forEach(item=>{ - // totalCount += item.children.length - // }) - // if (totalCount < memo.total_replies_count) { - // hasMoreComments = true; - // } - // } - // this.setState({ - // hasMoreComments, - // pageCount: 1, - // comments: memo_replies - // }) - // delete response.data.memo_replies; - // this.setState(response.data) - // const user = response.data.current_user; - // user.tidding_count = response.data.tidding_count; - // this.props.initCommonState(user) - } - this.setState({ - memoLoading: false - }) - - }).catch((error) => { - console.log(error) - }) - - this.fetchReplies() - - $('body>#root').on('onMemoDelete', (event) => { - // const val = $('body>#root').data('onMemoDelete') - const val = window.onMemoDelete ; - this.onMemoDelete( JSON.parse(decodeURIComponent(val)) ) - }) - - - } - - onPaginationChange = (pageCount) => { - this.setState({ pageCount }, () => { - this.fetchReplies() - }) - } - - componentWillUnmount() { - $('body>#root').off('onMemoDelete') - } - onMemoDelete(memo) { - const deleteUrl = `/commons/delete.json`; - // 获取memo list - axios.delete(deleteUrl, { data: { - object_id: memo.id, - object_type: 'message' - } - }) - .then((response) => { - const status = response.data.status - if (status === 0) { - - this.props.showNotification('删除成功'); - const props = Object.assign({}, this.props, {}) - this.props.toListPage( Object.assign({}, this.props.match.params, {'coursesId': this.state.memo.course_id} ) ) - - } else if (status === -1) { - this.props.showNotification('帖子已被删除'); - this.props.history.push(`/forums`) - } - }).catch((error) => { - console.log(error) - }) - } - - componentDidUpdate(prevProps, prevState, snapshot) { - // if (this.state.memo && this.state.memo.content - // && (!prevProps.memo || prevProps.memo.content != this.state.memo.content) ) { - if (this.state.memo && this.state.memo.content && prevState.memoLoading === true && this.state.memoLoading === false) { - // md渲染content,等xhr执行完(即memoLoading变化),memo.content更新后初始化md - - setTimeout(()=>{ - // var shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", { - // htmlDecode: "style,script,iframe", // you can filter tags decode - // taskList: true, - // tex: true, // 默认不解析 - // flowChart: true, // 默认不解析 - // sequenceDiagram: true // 默认不解析 - // }); - }, 200) - } - - } - - clickPraise(){ - const { memo } = this.state; - // const url = `/api/v1/discusses/${memo.id}/plus`; - const url = memo.user_praise ? '/praise_tread/unlike.json' : `/praise_tread/like.json`; - const _method = memo.user_praise ? axios.delete : axios.post - let _data = { - object_id: memo.id, - object_type: 'message', //Discuss - } - if (memo.user_praise) { - _data = { - data: _data - } - } - _method(url, { - ..._data - }, - { - - } - ).then((response) => { - - const newMemo = Object.assign({}, this.state.memo) - newMemo.praises_count = newMemo.user_praise ? newMemo.praises_count - 1 : newMemo.praises_count + 1 - newMemo.total_praises_count = newMemo.user_praise ? newMemo.total_praises_count - 1 : newMemo.total_praises_count + 1 - newMemo.user_praise = !newMemo.user_praise - this.setState({memo : newMemo }) - }).catch((error) => { - console.log(error) - }) - } - renderAttachment() { - const { memo } = this.state; - const attachments = [] - memo.attachments.forEach((item, index) => { - const ar = item.url.split('/') - const fileName = item.title || ar[ar.length - 1] - let filesize = 0 - if (item.filesize) { - filesize = item.filesize - // filesize = bytesToSize(item.filesize) - } - attachments.push( - // <p className="clearfix" key={index} > - // <a href={item.url} className="color-green clearfix notefileDownload"> - // <i className="iconfont icon-fujian color-green ml5 fl"></i> - // {fileName && <ConditionToolTip title={fileName} condition={fileName.length > 30 }> - // <span className="fl task-hide upload_item" style={{ color: '#333'}}>{fileName}</span> - // </ConditionToolTip>} - // <span className="fl" style={{ color: '#999', marginLeft: '6px'}}>{filesize? ` ${filesize.replace(' ', '')}` : ''}</span> - // </a> - // </p> - - <div className="color-grey df" key={index}> - <a className="color-grey "> - <i className="font-14 color-green iconfont icon-fujian mr8" aria-hidden="true"></i> - </a> - {/* {fileName && <ConditionToolTip title={fileName} condition={fileName.length > 30 }> </ConditionToolTip>} */} - <a href={item.url} title={fileName.length > 30 ? fileName : ''} - className="mr12 color9B9B overflowHidden1" length="58" style={{maxWidth: '480px'}}> - {fileName} - </a> - - - <span className="color656565 mt2 color-grey-6 font-12 mr8">{item.filesize}</span> - - </div> - ) - }) - return attachments; - } - // ------------------------------------------------------------------------------------------- comments START - // ------------------------------------------------------------------------------------------- comments START - transformReply = (reply, children = []) => { - const isAdmin = this.props.isAdmin() - const isSuperAdmin = this.props.isSuperAdmin() - return { - isSuperAdmin: isSuperAdmin, - admin: isAdmin, // - permission: true, // - children: children, - child_message_count: reply.total_count, - hidden: reply.is_hidden, - id: reply.id, - image_url: reply.author.image_url, - reward: null, // - time: moment(reply.created_on).fromNow(), - user_id: reply.author.id, - user_login: reply.author.login, - user_praise: reply.liked, - username: reply.author.name, - content: reply.content, - praise_count: reply.praises_count - } - } - - fetchReplies = () => { - const topicId = this.props.match.params.topicId - const url = `/messages/${topicId}/reply_list.json?page=${this.state.pageCount}&page_size=${REPLY_PAGE_COUNT}` - axios.get(url,{ - }) - .then((response) => { - const { replies, liked, total_replies_count, total_count } = response.data.data - - const memo = Object.assign({}, this.state.memo) - memo.user_praise = liked - memo.total_replies_count = total_replies_count; - this.setState({ - memo, - comments: generateComments(replies, this.transformReply, 'replies'), - // : this.state.comments.concat(comments), - total_count: total_count - }) - }).catch((error) => { - console.log(error) - }) - } - - _getUser() { - const { current_user } = this.props; - current_user.user_url = `/users/${current_user.login}`; - return current_user; - } - _findById = _findById - replyComment = (commentContent, id, editor) => { - const { showNotification } = this.props; - // if (!commentContent || commentContent.length === 0) { - // showNotification(NEED_TO_WRITE_CONTENT) - // return; - // } - - if (this.state.memo.id === id ) { // 回复帖子 - this.createNewComment(commentContent, id, editor); - return; - } - const url = `/messages/${id}/reply.json`; - - const { comments } = this.state; - const user = this._getUser(); - /* - 移除末尾的空行 - .replace(/(\n<p>\n\t<br \/>\n<\/p>)*$/g,''); - - */ - - commentContent = handleContentBeforeCreateSecondLevelComment(commentContent) - if (!commentContent) { - this.props.showNotification('不能为空') - return; - } - axios.post(url, { - content: commentContent - }, - { - } - ).then((response) => { - if (response.data.data.id) { - let newId = response.data.data.id; - const commentIndex = this._findById(id, comments); - const parentComment = comments[commentIndex] - - this.setState({ - // runTesting: false, - comments: addSecondLevelComment(comments, parentComment, commentIndex, newId, commentContent, user, editor) - }, ()=>{ - // keditor代码美化 - editor.html && window.prettyPrint() - }) - - const newMemo2 = Object.assign({}, this.state.memo); - newMemo2.total_replies_count = newMemo2.total_replies_count + 1; - this.setState({ - memo: newMemo2 - }) - } - - }).catch((error) => { - console.log(error) - }) - } - // 公共接口 --- 删除回复 - deleteComment = (parrentComment, childCommentId) => { - handleDeleteComment(this, parrentComment, childCommentId, 'message') - - } - // 公共接口 --- 回复点赞 - commentPraise = (discussId) => { - handleCommentPraise(this, discussId, 'message', (old_user_praise) => { - const newMemo2 = Object.assign({}, this.state.memo); - - newMemo2.total_praises_count = old_user_praise - ? newMemo2.total_praises_count - 1 : newMemo2.total_praises_count + 1; - this.setState({ - memo: newMemo2 - }) - }) - } - // 公共接口 --- 隐藏回复 - hiddenComment = (item, childCommentId) => { - handleHiddenComment(this, item, childCommentId, 'message') - } - createNewComment = (commentContent, id, editor) => { - let content = handleContentBeforeCreateNew(commentContent); - const { memo } = this.props; - - const url = `/messages/${id}/reply.json`; - - // const url = `/api/v1/memos/${memo.id}/reply`; - let { comments } = this.state; - axios.post(url, { - content: content - }, - { - } - ).then((response) => { - if (response.data.status === -1) { - console.error('服务端异常') - return; - } - // this.props.showNotification('帖子发表成功') - - if (response.data) { - const _id = response.data.data.id; - // ke - editor.html && editor.html(''); - editor.afterBlur && editor.afterBlur() - // md - editor.setValue && editor.setValue('') - - - const user = this._getUser(); - this.setState({ - comments: addNewComment(comments, _id, content, user, this.props.isSuperAdmin(), this) - }) - const newMemo2 = Object.assign({}, this.state.memo); - newMemo2.total_replies_count = newMemo2.total_replies_count + 1; - this.setState({ - memo: newMemo2 - }) - this.refs.editor.showEditor(); - this.refs.editor.close(); - - - } - }).catch((error) => { - console.log(error) - }) - } - - /** - * parent.isAllChildrenLoaded 为 true的时候,表示已经没有更多子回复了 - */ - loadMoreChildComments = (parent) => { - const url = `/messages/${parent.id}/reply_list.json?page=1&page_size=500` - axios.get(url,{ - }) - .then((response) => { - const { replies, liked, total_replies_count } = response.data.data - - // const memo = Object.assign({}, this.state.memo) - // memo.total_replies_count = total_replies_count; - this.setState({ - // memo, - comments: generateChildComments(replies, this.state.comments, parent, this.transformReply) - }) - }).catch((error) => { - console.log(error) - }) - } - // ------------------------------------------------------------------------------------------- comments END - // ------------------------------------------------------------------------------------------- comments END - // 置顶 - setTop(memo) { - // const params = { - // sticky: memo.sticky ? 0 : 1, - // } - // if (this.state.p_s_order) { - // params.order = this.state.p_s_order; - // } - // if (this.state.p_forum_id) { - // params.forum_id = this.state.p_forum_id; - // } - // let paramsUrl = urlStringify(params) - const set_top_or_down_Url = `/messages/${memo.id}/sticky_top.json`; - // 获取memo list - axios.put(set_top_or_down_Url, { - - }) - .then((response) => { - const status = response.data.status - if (status === 0) { - this.props.showNotification( memo.sticky ? '取消置顶成功' : '置顶成功'); - memo.sticky = memo.sticky ? false : true - this.setState({ - memo: Object.assign({}, memo) - }) - } - }).catch((error) => { - console.log(error) - }) - } - - setRewardDialogVisible = (visible) => { - this.setState({ - goldRewardDialogOpen: visible - }) - } - showRewardDialog = () => { - this.setState({ - goldRewardDialogOpen: true - }) - } - // --------------------------------------------------------------------------------------------帖子獎勵 END - showCommentInput = () => { - if (window.__useKindEditor === true) { - this.refs.editor.showEditor(); - } else { - this.refs.editor.showEditor(); - } - } - initReply = (parent) => { - if (!parent.isAllChildrenLoaded) { - this.loadMoreChildComments(parent) - } - } - - - render() { - const { match, history } = this.props - const { recommend_shixun, current_user,author_info } = this.props; - const { memo, comments, hasMoreComments, goldRewardDialogOpen, pageCount, total_count } = this.state; - const messageId = match.params.topicId - if (this.state.memoLoading || !current_user) { - return <div className="edu-back-white" id="forum_index_list"></div> - } - current_user.user_url = `/users/${current_user.login}`; - const isCurrentUserTheAuthor = current_user.login == memo.author.login - const isAdmin = this.props.isAdmin() - // TODO 图片上传地址 - const courseId=this.props.match.params.coursesId; - const boardId = this.props.match.params.boardId - return ( - <div className="edu-back-white edu-class-container edu-position course-message topicDetail" id="forum_index_list"> {/* fl with100 */} - <style>{` - .topicDetail #forum_list .return_btn.no_mr { - margin-right: 1px; - } - /* 有内容时,编辑器下方的边框*/ - .topicDetail .borderBottom.commentInputs { - border-bottom: 1px solid rgb(238, 238, 238); - } - .independent { - background: rgb(250, 250, 250); - padding-bottom: 20px; - margin-bottom: 0px !important; - } - - .course-message.topicDetail .panel-comment_item .comment_orig_content { - width: 1072px; - } - `}</style> - <CBreadcrumb className={'independent'} items={[ - { to: current_user.first_category_url, name: this.props.coursedata.name}, - { to: `/courses/${courseId}/boards/${boardId}`, name: memo.board_name }, - { name: '帖子详情'} - ]}></CBreadcrumb> - - <SendToCourseModal - ref="sendToCourseModal" - {...this.props} - moduleName="帖子" - selectedMessageIds={[messageId]} - ></SendToCourseModal> - <div className="clearfix"> - <div id="forum_list" className="forum_table mh650"> - <div className="padding30 bor-bottom-greyE" style={{paddingBottom: '20px'}}> - <div className="font-16 cdefault clearfix pr pr35"> - <span className="noteDetailTitle">{memo.subject}</span> - { !!memo.sticky && <span className="btn-cir btn-cir-red ml10" - style={{position: 'relative', bottom: '4px'}}>置顶</span>} - { !!memo.reward && <span className="color-orange font-14 ml15" - data-tip-down={`获得平台奖励金币:${memo.reward}`} > - <i className="iconfont icon-gift mr5"></i>{memo.reward} - </span> } - {/* || current_user.user_id === author_info.user_id */} - { current_user && (isAdmin || isCurrentUserTheAuthor) && - <div className="edu-position-hidebox" style={{position: 'absolute', right: '2px',top:'4px'}}> - <a href="javascript:void(0);"><i className="fa fa-bars font-16"></i></a> - <ul className="edu-position-hide undis"> - - { ( isCurrentUserTheAuthor || isAdmin ) && - <li><a - onClick={() => this.props.toEditPage( Object.assign({}, this.props.match.params, {'coursesId': this.state.memo.course_id}) ) } - >编 辑</a></li>} - { isAdmin && - ( memo.sticky == true ? - <li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>取消置顶</a></li> - : - <li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>置 顶</a></li> ) - } - { isAdmin && - <li><a href="javascript:void(0);" onClick={() => this.refs.sendToCourseModal.setVisible(true)}>发 送</a></li> - } - { ( isCurrentUserTheAuthor || isAdmin ) && <li> - <a href="javascript:void(0)" onClick={() => - window.delete_confirm_box_2_react(`onMemoDelete`, '您确定要删除吗?' , memo)}> - - 删 除</a> - </li> - } - </ul> - </div> - } - - </div> - <div className="color-grey-9 clearfix"> - <span className="fl" style={{marginTop: "2px"}}>{moment(memo.created_on).fromNow()} 发布</span> - <div className="fr"> - - </div> - </div> - - <div className="color-grey-9 clearfix"> - <span className="fl" style={{marginTop: '4px'}}> - {/* { current_user.admin && <Tooltip title={ "帖子奖励" }> - <span className="noteDetailNum rightline cdefault" style={{padding: '0 4px', cursor: 'pointer'}}> - <i className="iconfont icon-jiangli mr5" onClick={this.showRewardDialog}></i> - </span> - </Tooltip> } */} - <Tooltip title={"浏览数"}> - <span className={`noteDetailNum `} style={{paddingLeft: '0px'}}> - <i className="iconfont icon-liulanyan mr5"></i> - <span style={{ top: "1px", position: "relative" }}>{memo.visits || '1'}</span> - </span> - </Tooltip> - { !!memo.total_replies_count && - <Tooltip title={"回复数"}> - <a href="javascript:void(0)" className="noteDetailNum"> - <i className="iconfont icon-huifu1 mr5" onClick={this.showCommentInput}></i> - <span style={{ top: "2px", position: "relative" }}>{ memo.total_replies_count }</span> - </a> - </Tooltip> - } - {!!memo.total_praises_count && - <Tooltip title={"点赞数"}> - <span className={`noteDetailNum `} style={{}}> - <i className="iconfont icon-dianzan-xian mr5"></i> - <span style={{ top: "2px", position: "relative" }}>{ memo.total_praises_count }</span> - </span> - </Tooltip> - } - </span> - <div className="fr"> - {/* || current_user.user_id === author_info.user_id */} - <a className={`task-hide fr return_btn color-grey-6 ${ current_user && (isAdmin - ) ? '': 'no_mr'} `} onClick={() => this.props.toListPage(Object.assign({}, this.props.match.params, {'coursesId': this.state.memo.course_id})) } > - 返回 - </a> - </div> - </div> - </div> - - - <div className="padding30 memoContent new_li" style={{ paddingBottom: '10px'}}> - {memo.is_md == true ? <MarkdownToHtml content={memo.content}></MarkdownToHtml> : - <div dangerouslySetInnerHTML={{ __html: memo.content }}></div> - } - </div> - <div className="padding30 bor-bottom-greyE" style={{paddingTop: '2px'}}> - <div className="mt10 mb20"> - {/* ${memo.user_praise ? '' : ''} */} - <Tooltip title={`${memo.liked ? '取消点赞' : '点赞'}`}> - <p className={`noteDetailPoint ${memo.user_praise ? 'Pointed' : ''}`} onClick={()=>{this.clickPraise()}} > - <i className="iconfont icon-dianzan"></i><br/> - <span>{memo.praises_count}</span> - </p> - </Tooltip> - </div> - - { memo.attachments && !!memo.attachments.length && - <div> - {this.renderAttachment()} - </div> - } - </div> - - <MemoDetailMDEditor ref="editor" memo={memo} usingMockInput={true} placeholder="说点什么" - height={160} showError={true} buttonText={'发表'} className={comments && comments.length && 'borderBottom'}></MemoDetailMDEditor> - - {/* onClick={ this.createNewComment } - enableReplyTo={true} - */} - <div className="padding20 memoReplies commentsDelegateParent comments_hideSecondReplyUserHeader" - style={{ display: (comments && !!comments.length) ? 'block' : 'none', paddingBottom: '0px' }}> - <div className="replies_count"> - <span className="labal font-16">全部回复</span> - <span className="count font-16">({memo.total_replies_count})</span> - </div> - - <Comments comments={comments} user={current_user} - replyComment={this.replyComment} - deleteComment={this.deleteComment} - commentPraise={this.commentPraise} - rewardCode={this.rewardCode} - hiddenComment={this.hiddenComment} - buttonText={'发表'} - - usingAntdModal={true} - isChildCommentPagination={true} - loadMoreChildComments={this.loadMoreChildComments} - initReply={this.initReply} - showRewardButton={false} - - onlySuperAdminCouldHide={true} - ></Comments> - - - {/* { true ? : - <div className="memoMore"> - <div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div> - </div>} */} - </div> - - - <div className="memoMore" style={{'margin-top': '20px'}}> - { total_count > REPLY_PAGE_COUNT && - <Pagination showQuickJumper onChange={this.onPaginationChange} current={pageCount} total={total_count} pageSize={10}/> - } - <div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div> - </div> - - </div> - </div> - </div> - - ); - } -} - -export default ImageLayerOfCommentHOC() ( RouteHOC()(TopicDetail) ); +import React, { Component } from 'react'; +import { Redirect } from 'react-router'; + +import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom"; + +import PropTypes from 'prop-types'; + +import classNames from 'classnames' + +import axios from 'axios' + +import moment from 'moment' + +import Comments from '../../comment/Comments' + +import update from 'immutability-helper' +import RewardDialog from '../../common/RewardDialog'; +import {ImageLayerOfCommentHOC} from '../../page/layers/ImageLayerOfCommentHOC' + +import MemoDetailMDEditor from '../../forums/MemoDetailMDEditor' +import { RouteHOC } from './common.js' +import '../../forums/Post.css' +import '../../forums/RightSection.css' +import './TopicDetail.css' +import '../common/courseMessage.css' +import { Pagination, Tooltip } from 'antd' +import { bytesToSize, ConditionToolTip, markdownToHTML, MarkdownToHtml } from 'educoder' +import SendToCourseModal from '../coursesPublic/modal/SendToCourseModal' +import CBreadcrumb from '../common/CBreadcrumb' +import { generateComments, generateChildComments, _findById, handleContentBeforeCreateNew, addNewComment + , addSecondLevelComment, NEED_TO_WRITE_CONTENT, handleContentBeforeCreateSecondLevelComment + , handleDeleteComment, handleCommentPraise, handleHiddenComment } from '../common/CommentsHelper' + +const $ = window.$ +const REPLY_PAGE_COUNT = 10 +function urlStringify(params) { + let noParams = true; + let paramsUrl = ''; + for (let key in params) { + noParams = false; + paramsUrl += `${key}=${params[key]}&` + } + if (noParams) { + return ''; + } + paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1); + return paramsUrl; +} +class TopicDetail extends Component { + constructor(props) { + super(props) + + this.state = { + memo: {}, + memoLoading: true, + hasMoreComments: false, + pageCount: 1, + comments: [], + goldRewardDialogOpen: false, + } + } + componentDidMount() { + window.$("html,body").animate({"scrollTop":0}) + + const topicId = this.props.match.params.topicId + const bid = this.props.match.params.boardId + + const memoUrl = `/messages/${topicId}.json`; + this.setState({ + memoLoading: true + }) + axios.get(memoUrl,{ + }) + .then((response) => { + + if (response.data.status === -1) { + setTimeout(() => { + this.props.showNotification('帖子不存在!') + }, 300) + // this.props.toListPage(response.data.data.course_id, bid) + return; + } else { + + this.setState({ + memo: Object.assign({}, { + ...response.data.data, + replies_count: response.data.data.total_replies_count + }, {...this.state.memo}) + }, () => { + }) + + // const { memo_replies, memo } = response.data; + // let hasMoreComments = false; + // if (memo_replies && memo_replies.length === 10 && memo.total_replies_count > 10) { + // // 遍历一遍,计算下是否还有评论未加载 + // let totalCount = 10; + // memo_replies.forEach(item=>{ + // totalCount += item.children.length + // }) + // if (totalCount < memo.total_replies_count) { + // hasMoreComments = true; + // } + // } + // this.setState({ + // hasMoreComments, + // pageCount: 1, + // comments: memo_replies + // }) + // delete response.data.memo_replies; + // this.setState(response.data) + // const user = response.data.current_user; + // user.tidding_count = response.data.tidding_count; + // this.props.initCommonState(user) + } + this.setState({ + memoLoading: false + }) + + }).catch((error) => { + console.log(error) + }) + + this.fetchReplies() + + $('body>#root').on('onMemoDelete', (event) => { + // const val = $('body>#root').data('onMemoDelete') + const val = window.onMemoDelete ; + this.onMemoDelete( JSON.parse(decodeURIComponent(val)) ) + }) + + + } + + onPaginationChange = (pageCount) => { + this.setState({ pageCount }, () => { + this.fetchReplies() + }) + } + + componentWillUnmount() { + $('body>#root').off('onMemoDelete') + } + onMemoDelete(memo) { + const deleteUrl = `/commons/delete.json`; + // 获取memo list + axios.delete(deleteUrl, { data: { + object_id: memo.id, + object_type: 'message' + } + }) + .then((response) => { + const status = response.data.status + if (status === 0) { + + this.props.showNotification('删除成功'); + const props = Object.assign({}, this.props, {}) + this.props.toListPage( Object.assign({}, this.props.match.params, {'coursesId': this.state.memo.course_id} ) ) + + } else if (status === -1) { + this.props.showNotification('帖子已被删除'); + this.props.history.push(`/forums`) + } + }).catch((error) => { + console.log(error) + }) + } + + componentDidUpdate(prevProps, prevState, snapshot) { + // if (this.state.memo && this.state.memo.content + // && (!prevProps.memo || prevProps.memo.content != this.state.memo.content) ) { + if (this.state.memo && this.state.memo.content && prevState.memoLoading === true && this.state.memoLoading === false) { + // md渲染content,等xhr执行完(即memoLoading变化),memo.content更新后初始化md + + setTimeout(()=>{ + // var shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", { + // htmlDecode: "style,script,iframe", // you can filter tags decode + // taskList: true, + // tex: true, // 默认不解析 + // flowChart: true, // 默认不解析 + // sequenceDiagram: true // 默认不解析 + // }); + }, 200) + } + + } + + clickPraise(){ + const { memo } = this.state; + // const url = `/api/v1/discusses/${memo.id}/plus`; + const url = memo.user_praise ? '/praise_tread/unlike.json' : `/praise_tread/like.json`; + const _method = memo.user_praise ? axios.delete : axios.post + let _data = { + object_id: memo.id, + object_type: 'message', //Discuss + } + if (memo.user_praise) { + _data = { + data: _data + } + } + _method(url, { + ..._data + }, + { + + } + ).then((response) => { + + const newMemo = Object.assign({}, this.state.memo) + newMemo.praises_count = newMemo.user_praise ? newMemo.praises_count - 1 : newMemo.praises_count + 1 + newMemo.total_praises_count = newMemo.user_praise ? newMemo.total_praises_count - 1 : newMemo.total_praises_count + 1 + newMemo.user_praise = !newMemo.user_praise + this.setState({memo : newMemo }) + }).catch((error) => { + console.log(error) + }) + } + renderAttachment() { + const { memo } = this.state; + const attachments = [] + memo.attachments.forEach((item, index) => { + const ar = item.url.split('/') + const fileName = item.title || ar[ar.length - 1] + let filesize = 0 + if (item.filesize) { + filesize = item.filesize + // filesize = bytesToSize(item.filesize) + } + attachments.push( + // <p className="clearfix" key={index} > + // <a href={item.url} className="color-green clearfix notefileDownload"> + // <i className="iconfont icon-fujian color-green ml5 fl"></i> + // {fileName && <ConditionToolTip title={fileName} condition={fileName.length > 30 }> + // <span className="fl task-hide upload_item" style={{ color: '#333'}}>{fileName}</span> + // </ConditionToolTip>} + // <span className="fl" style={{ color: '#999', marginLeft: '6px'}}>{filesize? ` ${filesize.replace(' ', '')}` : ''}</span> + // </a> + // </p> + + <div className="color-grey df" key={index}> + <a className="color-grey "> + <i className="font-14 color-green iconfont icon-fujian mr8" aria-hidden="true"></i> + </a> + {/* {fileName && <ConditionToolTip title={fileName} condition={fileName.length > 30 }> </ConditionToolTip>} */} + <a href={item.url} title={fileName.length > 30 ? fileName : ''} + className="mr12 color9B9B overflowHidden1" length="58" style={{maxWidth: '480px'}}> + {fileName} + </a> + + + <span className="color656565 mt2 color-grey-6 font-12 mr8">{item.filesize}</span> + + </div> + ) + }) + return attachments; + } + // ------------------------------------------------------------------------------------------- comments START + // ------------------------------------------------------------------------------------------- comments START + transformReply = (reply, children = []) => { + const isAdmin = this.props.isAdmin() + const isSuperAdmin = this.props.isSuperAdmin() + return { + isSuperAdmin: isSuperAdmin, + admin: isAdmin, // + permission: true, // + children: children, + child_message_count: reply.total_count, + hidden: reply.is_hidden, + id: reply.id, + image_url: reply.author.image_url, + reward: null, // + time: moment(reply.created_on).fromNow(), + user_id: reply.author.id, + user_login: reply.author.login, + user_praise: reply.liked, + username: reply.author.name, + content: reply.content, + praise_count: reply.praises_count + } + } + + fetchReplies = () => { + const topicId = this.props.match.params.topicId + const url = `/messages/${topicId}/reply_list.json?page=${this.state.pageCount}&page_size=${REPLY_PAGE_COUNT}` + axios.get(url,{ + }) + .then((response) => { + const { replies, liked, total_replies_count, total_count } = response.data.data + + const memo = Object.assign({}, this.state.memo) + memo.user_praise = liked + memo.total_replies_count = total_replies_count; + this.setState({ + memo, + comments: generateComments(replies, this.transformReply, 'replies'), + // : this.state.comments.concat(comments), + total_count: total_count + }) + }).catch((error) => { + console.log(error) + }) + } + + _getUser() { + const { current_user } = this.props; + current_user.user_url = `/users/${current_user.login}`; + return current_user; + } + _findById = _findById + replyComment = (commentContent, id, editor) => { + const { showNotification } = this.props; + // if (!commentContent || commentContent.length === 0) { + // showNotification(NEED_TO_WRITE_CONTENT) + // return; + // } + + if (this.state.memo.id === id ) { // 回复帖子 + this.createNewComment(commentContent, id, editor); + return; + } + const url = `/messages/${id}/reply.json`; + + const { comments } = this.state; + const user = this._getUser(); + /* + 移除末尾的空行 + .replace(/(\n<p>\n\t<br \/>\n<\/p>)*$/g,''); + + */ + + commentContent = handleContentBeforeCreateSecondLevelComment(commentContent) + if (!commentContent) { + this.props.showNotification('不能为空') + return; + } + axios.post(url, { + content: commentContent + }, + { + } + ).then((response) => { + if (response.data.data.id) { + let newId = response.data.data.id; + const commentIndex = this._findById(id, comments); + const parentComment = comments[commentIndex] + + this.setState({ + // runTesting: false, + comments: addSecondLevelComment(comments, parentComment, commentIndex, newId, commentContent, user, editor) + }, ()=>{ + // keditor代码美化 + editor.html && window.prettyPrint() + }) + + const newMemo2 = Object.assign({}, this.state.memo); + newMemo2.total_replies_count = newMemo2.total_replies_count + 1; + this.setState({ + memo: newMemo2 + }) + } + + }).catch((error) => { + console.log(error) + }) + } + // 公共接口 --- 删除回复 + deleteComment = (parrentComment, childCommentId) => { + handleDeleteComment(this, parrentComment, childCommentId, 'message') + + } + // 公共接口 --- 回复点赞 + commentPraise = (discussId) => { + handleCommentPraise(this, discussId, 'message', (old_user_praise) => { + const newMemo2 = Object.assign({}, this.state.memo); + + newMemo2.total_praises_count = old_user_praise + ? newMemo2.total_praises_count - 1 : newMemo2.total_praises_count + 1; + this.setState({ + memo: newMemo2 + }) + }) + } + // 公共接口 --- 隐藏回复 + hiddenComment = (item, childCommentId) => { + handleHiddenComment(this, item, childCommentId, 'message') + } + createNewComment = (commentContent, id, editor) => { + let content = handleContentBeforeCreateNew(commentContent); + const { memo } = this.props; + + const url = `/messages/${id}/reply.json`; + + // const url = `/api/v1/memos/${memo.id}/reply`; + let { comments } = this.state; + axios.post(url, { + content: content + }, + { + } + ).then((response) => { + if (response.data.status === -1) { + console.error('服务端异常') + return; + } + // this.props.showNotification('帖子发表成功') + + if (response.data) { + const _id = response.data.data.id; + // ke + editor.html && editor.html(''); + editor.afterBlur && editor.afterBlur() + // md + editor.setValue && editor.setValue('') + + + const user = this._getUser(); + this.setState({ + comments: addNewComment(comments, _id, content, user, this.props.isSuperAdmin(), this) + }) + const newMemo2 = Object.assign({}, this.state.memo); + newMemo2.total_replies_count = newMemo2.total_replies_count + 1; + this.setState({ + memo: newMemo2 + }) + this.refs.editor.showEditor(); + this.refs.editor.close(); + + + } + }).catch((error) => { + console.log(error) + }) + } + + /** + * parent.isAllChildrenLoaded 为 true的时候,表示已经没有更多子回复了 + */ + loadMoreChildComments = (parent) => { + const url = `/messages/${parent.id}/reply_list.json?page=1&page_size=500` + axios.get(url,{ + }) + .then((response) => { + const { replies, liked, total_replies_count } = response.data.data + + // const memo = Object.assign({}, this.state.memo) + // memo.total_replies_count = total_replies_count; + this.setState({ + // memo, + comments: generateChildComments(replies, this.state.comments, parent, this.transformReply) + }) + }).catch((error) => { + console.log(error) + }) + } + // ------------------------------------------------------------------------------------------- comments END + // ------------------------------------------------------------------------------------------- comments END + // 置顶 + setTop(memo) { + // const params = { + // sticky: memo.sticky ? 0 : 1, + // } + // if (this.state.p_s_order) { + // params.order = this.state.p_s_order; + // } + // if (this.state.p_forum_id) { + // params.forum_id = this.state.p_forum_id; + // } + // let paramsUrl = urlStringify(params) + const set_top_or_down_Url = `/messages/${memo.id}/sticky_top.json`; + // 获取memo list + axios.put(set_top_or_down_Url, { + + }) + .then((response) => { + const status = response.data.status + if (status === 0) { + this.props.showNotification( memo.sticky ? '取消置顶成功' : '置顶成功'); + memo.sticky = memo.sticky ? false : true + this.setState({ + memo: Object.assign({}, memo) + }) + } + }).catch((error) => { + console.log(error) + }) + } + + setRewardDialogVisible = (visible) => { + this.setState({ + goldRewardDialogOpen: visible + }) + } + showRewardDialog = () => { + this.setState({ + goldRewardDialogOpen: true + }) + } + // --------------------------------------------------------------------------------------------帖子獎勵 END + showCommentInput = () => { + if (window.__useKindEditor === true) { + this.refs.editor.showEditor(); + } else { + this.refs.editor.showEditor(); + } + } + initReply = (parent) => { + if (!parent.isAllChildrenLoaded) { + this.loadMoreChildComments(parent) + } + } + + + render() { + const { match, history } = this.props + const { recommend_shixun, current_user,author_info } = this.props; + const { memo, comments, hasMoreComments, goldRewardDialogOpen, pageCount, total_count } = this.state; + const messageId = match.params.topicId + if (this.state.memoLoading || !current_user) { + return <div className="edu-back-white" id="forum_index_list"></div> + } + current_user.user_url = `/users/${current_user.login}`; + const isCurrentUserTheAuthor = current_user.login == memo.author.login + const isAdmin = this.props.isAdmin() + // TODO 图片上传地址 + const courseId=this.props.match.params.coursesId; + const boardId = this.props.match.params.boardId + return ( + <div className="edu-back-white edu-class-container edu-position course-message topicDetail" id="forum_index_list"> {/* fl with100 */} + <style>{` + .topicDetail #forum_list .return_btn.no_mr { + margin-right: 1px; + } + /* 有内容时,编辑器下方的边框*/ + .topicDetail .borderBottom.commentInputs { + border-bottom: 1px solid rgb(238, 238, 238); + } + .independent { + background: rgb(250, 250, 250); + padding-bottom: 20px; + margin-bottom: 0px !important; + } + + .course-message.topicDetail .panel-comment_item .comment_orig_content { + width: 1072px; + } + `}</style> + <CBreadcrumb className={'independent'} items={[ + { to: current_user&¤t_user.first_category_url, name: this.props.coursedata.name}, + { to: `/courses/${courseId}/boards/${boardId}`, name: memo.board_name }, + { name: '帖子详情'} + ]}></CBreadcrumb> + + <SendToCourseModal + ref="sendToCourseModal" + {...this.props} + moduleName="帖子" + selectedMessageIds={[messageId]} + ></SendToCourseModal> + <div className="clearfix"> + <div id="forum_list" className="forum_table mh650"> + <div className="padding30 bor-bottom-greyE" style={{paddingBottom: '20px'}}> + <div className="font-16 cdefault clearfix pr pr35"> + <span className="noteDetailTitle">{memo.subject}</span> + { !!memo.sticky && <span className="btn-cir btn-cir-red ml10" + style={{position: 'relative', bottom: '4px'}}>置顶</span>} + { !!memo.reward && <span className="color-orange font-14 ml15" + data-tip-down={`获得平台奖励金币:${memo.reward}`} > + <i className="iconfont icon-gift mr5"></i>{memo.reward} + </span> } + {/* || current_user.user_id === author_info.user_id */} + { current_user && (isAdmin || isCurrentUserTheAuthor) && + <div className="edu-position-hidebox" style={{position: 'absolute', right: '2px',top:'4px'}}> + <a href="javascript:void(0);"><i className="fa fa-bars font-16"></i></a> + <ul className="edu-position-hide undis"> + + { ( isCurrentUserTheAuthor || isAdmin ) && + <li><a + onClick={() => this.props.toEditPage( Object.assign({}, this.props.match.params, {'coursesId': this.state.memo.course_id}) ) } + >编 辑</a></li>} + { isAdmin && + ( memo.sticky == true ? + <li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>取消置顶</a></li> + : + <li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>置 顶</a></li> ) + } + { isAdmin && + <li><a href="javascript:void(0);" onClick={() => this.refs.sendToCourseModal.setVisible(true)}>发 送</a></li> + } + { ( isCurrentUserTheAuthor || isAdmin ) && <li> + <a href="javascript:void(0)" onClick={() => + window.delete_confirm_box_2_react(`onMemoDelete`, '您确定要删除吗?' , memo)}> + + 删 除</a> + </li> + } + </ul> + </div> + } + + </div> + <div className="color-grey-9 clearfix"> + <span className="fl" style={{marginTop: "2px"}}>{moment(memo.created_on).fromNow()} 发布</span> + <div className="fr"> + + </div> + </div> + + <div className="color-grey-9 clearfix"> + <span className="fl" style={{marginTop: '4px'}}> + {/* { current_user.admin && <Tooltip title={ "帖子奖励" }> + <span className="noteDetailNum rightline cdefault" style={{padding: '0 4px', cursor: 'pointer'}}> + <i className="iconfont icon-jiangli mr5" onClick={this.showRewardDialog}></i> + </span> + </Tooltip> } */} + <Tooltip title={"浏览数"}> + <span className={`noteDetailNum `} style={{paddingLeft: '0px'}}> + <i className="iconfont icon-liulanyan mr5"></i> + <span style={{ top: "1px", position: "relative" }}>{memo.visits || '1'}</span> + </span> + </Tooltip> + { !!memo.total_replies_count && + <Tooltip title={"回复数"}> + <a href="javascript:void(0)" className="noteDetailNum"> + <i className="iconfont icon-huifu1 mr5" onClick={this.showCommentInput}></i> + <span style={{ top: "2px", position: "relative" }}>{ memo.total_replies_count }</span> + </a> + </Tooltip> + } + {!!memo.total_praises_count && + <Tooltip title={"点赞数"}> + <span className={`noteDetailNum `} style={{}}> + <i className="iconfont icon-dianzan-xian mr5"></i> + <span style={{ top: "2px", position: "relative" }}>{ memo.total_praises_count }</span> + </span> + </Tooltip> + } + </span> + <div className="fr"> + {/* || current_user.user_id === author_info.user_id */} + <a className={`task-hide fr return_btn color-grey-6 ${ current_user && (isAdmin + ) ? '': 'no_mr'} `} onClick={() => this.props.toListPage(Object.assign({}, this.props.match.params, {'coursesId': this.state.memo.course_id})) } > + 返回 + </a> + </div> + </div> + </div> + + + <div className="padding30 memoContent new_li" style={{ paddingBottom: '10px'}}> + {memo.is_md == true ? <MarkdownToHtml content={memo.content}></MarkdownToHtml> : + <div dangerouslySetInnerHTML={{ __html: memo.content }}></div> + } + </div> + <div className="padding30 bor-bottom-greyE" style={{paddingTop: '2px'}}> + <div className="mt10 mb20"> + {/* ${memo.user_praise ? '' : ''} */} + <Tooltip title={`${memo.liked ? '取消点赞' : '点赞'}`}> + <p className={`noteDetailPoint ${memo.user_praise ? 'Pointed' : ''}`} onClick={()=>{this.clickPraise()}} > + <i className="iconfont icon-dianzan"></i><br/> + <span>{memo.praises_count}</span> + </p> + </Tooltip> + </div> + + { memo.attachments && !!memo.attachments.length && + <div> + {this.renderAttachment()} + </div> + } + </div> + + <MemoDetailMDEditor ref="editor" memo={memo} usingMockInput={true} placeholder="说点什么" + height={160} showError={true} buttonText={'发表'} className={comments && comments.length && 'borderBottom'}></MemoDetailMDEditor> + + {/* onClick={ this.createNewComment } + enableReplyTo={true} + */} + <div className="padding20 memoReplies commentsDelegateParent comments_hideSecondReplyUserHeader" + style={{ display: (comments && !!comments.length) ? 'block' : 'none', paddingBottom: '0px' }}> + <div className="replies_count"> + <span className="labal font-16">全部回复</span> + <span className="count font-16">({memo.total_replies_count})</span> + </div> + + <Comments comments={comments} user={current_user} + replyComment={this.replyComment} + deleteComment={this.deleteComment} + commentPraise={this.commentPraise} + rewardCode={this.rewardCode} + hiddenComment={this.hiddenComment} + buttonText={'发表'} + + usingAntdModal={true} + isChildCommentPagination={true} + loadMoreChildComments={this.loadMoreChildComments} + initReply={this.initReply} + showRewardButton={false} + + onlySuperAdminCouldHide={true} + ></Comments> + + + {/* { true ? : + <div className="memoMore"> + <div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div> + </div>} */} + </div> + + + <div className="memoMore" style={{'margin-top': '20px'}}> + { total_count > REPLY_PAGE_COUNT && + <Pagination showQuickJumper onChange={this.onPaginationChange} current={pageCount} total={total_count} pageSize={10}/> + } + <div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div> + </div> + + </div> + </div> + </div> + + ); + } +} + +export default ImageLayerOfCommentHOC() ( RouteHOC()(TopicDetail) ); diff --git a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js index eb53fa373..4d82f4182 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js @@ -223,7 +223,7 @@ class CommonWorkDetailIndex extends Component{ } `}</style> {current_user && <CBreadcrumb items={[ - { to: current_user.first_category_url , name: course_name}, + { to: current_user&¤t_user.first_category_url , name: course_name}, { to: `/courses/${courseId}/${moduleEngName}/${category_id}`, name: category_name }, window.location.pathname.indexOf('appraise') == -1 ? { } : { to: `/courses/${courseId}/${moduleEngName}/${workId}/list`, name: '作业详情' }, // 1. 与上一条联动,当匿评他人作品时,TA人作品的作者真实姓名切换为“匿名” diff --git a/public/react/src/modules/courses/exercise/ExerciseNew.js b/public/react/src/modules/courses/exercise/ExerciseNew.js index f5332d56e..dac54f9e9 100644 --- a/public/react/src/modules/courses/exercise/ExerciseNew.js +++ b/public/react/src/modules/courses/exercise/ExerciseNew.js @@ -1,571 +1,571 @@ -import React,{ Component } from "react"; - -import { - Form, Input, InputNumber, Switch, Radio, - Slider, Button, Upload, Icon, Rate, Checkbox, message, - Row, Col, Select, Modal, Tooltip -} from 'antd'; -import { Q_TYPE_SINGLE, Q_TYPE_MULTI, Q_TYPE_JUDGE, Q_TYPE_NULL, Q_TYPE_MAIN, Q_TYPE_SHIXUN } from './new/common' -import TPMMDEditor from '../../tpm/challengesnew/TPMMDEditor'; -import axios from 'axios' -// import './board.css' -import "../common/formCommon.css" - -// import { RouteHOC } from './common.js' -import CBreadcrumb from '../common/CBreadcrumb' -import {getUrl, ActionBtn} from 'educoder'; - -import SingleEditor from './new/SingleEditor' -import SingleDisplay from './new/SingleDisplay' -import JudgeEditor from './new/JudgeEditor' -import JudgeDisplay from './new/JudgeDisplay' -import NullEditor from './new/NullEditor' -import NullDisplay from './new/NullDisplay' -import MainEditor from './new/MainEditor' -import MainDisplay from './new/MainDisplay' -import ShixunEditor from './new/ShixunEditor' -import ShixunDisplay from './new/ShixunDisplay' - -import ShixunChooseModal from '../coursesPublic/ShixunChooseModal' -import update from 'immutability-helper' -import './new/common.css' -import '../css/Courses.css' -const { TextArea } = Input; -const confirm = Modal.confirm; -const $ = window.$ -const { Option } = Select; - -class ExerciceNew extends Component{ - constructor(props){ - super(props); - - - this.state = { - exercise_questions: [], - exercise_name: '', - exercise_description: '', - exercise_types: {}, - editMode: !this.props.match.params.Id, - } - } - fetchExercise = () => { - const Id = this.props.match.params.Id - this.isEdit = !!Id - if (Id) { - const url = `/exercises/${Id}/edit.json` - axios.get(url) - .then((response) => { - if (response.data.status == 0) { - const { exercise, ...others } = response.data - this.setState({ - ...exercise, - ...others, - editMode: false - }) - } - }) - .catch(function (error) { - console.log(error); - }); - } else { - const courseId=this.props.match.params.coursesId; - - const newUrl = `/courses/${courseId}/exercises/new.json` - axios.get(newUrl) - .then((response) => { - if (response.data.status == 0) { - this.setState({ - ...response.data - }) - } - }) - .catch(function (error) { - console.log(error); - }); - } - } - componentDidMount = () => { - this.fetchExercise() - } - handleSubmit = (e) => { - - } - onSaveExercise = () => { - const { exercise_name, exercise_description } = this.state; - const exercise_id = this.props.match.params.Id - const courseId = this.props.match.params.coursesId - if (this.isEdit) { - const editUrl = `/exercises/${exercise_id}.json` - axios.put(editUrl, { - exercise_name, - exercise_description - }) - .then((response) => { - if (response.data.status == 0) { - this.setState({editMode: false}) - this.props.showNotification('试卷编辑成功') - } - }) - .catch(function (error) { - console.log(error); - }); - } else { - const url = `/courses/${courseId}/exercises.json` - axios.post(url, { - exercise_name, - exercise_description - }) - .then((response) => { - if (response.data.status == 0) { - this.setState({editMode: false}) - - this.props.showNotification('试卷新建成功') - const exercise_id = response.data.data.exercise_id; - this.isEdit = true; - - this.props.history.replace(`/courses/${courseId}/exercises/${exercise_id}/edit`); - - } - }) - .catch(function (error) { - console.log(error); - }); - } - } - exercise_name_change = (e) => { - this.setState({exercise_name: e.target.value}) - } - exercise_description_change = (e) => { - this.setState({exercise_description: e.target.value}) - } - // #问题的类型,0为单选题,1为多选题,2为判断题,3为填空题,4为主观题,5为实训题 - _checkIsEditing = () => { - if (this.editingId && $(this.editingId).length ) { - this.props.showNotification('请先保存或取消当前正在编辑的问题。') - $("html").animate({ scrollTop: $(this.editingId).offset().top - 100}) - return true - } - return false - } - onEditorCancel = () => { - this.editingId = null; - // 找到编辑或新建的item,新建就删掉item,编辑就isNew改为false - const { exercise_questions } = this.state - let index = -1; - for(let i = 0; i < exercise_questions.length; i++) { - if (exercise_questions[i].isNew == true) { - index = i; - break; - } - } - if (exercise_questions[index].question_id) { // 编辑 - this.setState( - (prevState) => ({ - exercise_questions : update(prevState.exercise_questions, {[index]: { isNew: {$set: false}}}) - // update(prevState.exercise_questions, {$splice: [[index, 1]]}) - }) - ) - } else { // 新建 - this.setState( - (prevState) => ({ - exercise_questions : update(prevState.exercise_questions, {$splice: [[index, 1]]}) - }) - ) - } - } - addQuestion = (question_id_to_insert_after, type) => { - if (!this.isEdit) { - this.props.showNotification('请先输入试卷标题,并保存试卷') - return; - } - if (this._checkIsEditing()) { - return; - } - if (type == Q_TYPE_SHIXUN) { - this.addShixun(question_id_to_insert_after) - } else { - this.addEditingQuestion(type, question_id_to_insert_after) - } - } - chooseShixun = (array) => { - this.addEditingQuestion(Q_TYPE_SHIXUN, this.question_id_to_insert_after, { - shixun_id: array[0] - }) - } - chooseShixunSuccess = () => { - this.refs.shixunChooseModal.setVisible(false) - } - addShixun = (question_id_to_insert_after) => { - if (!this.isEdit) { - this.props.showNotification('请先输入试卷标题,并保存试卷') - return; - } - // TODO 弹框选择实训 - if (this._checkIsEditing()) { - return; - } - this.refs.shixunChooseModal.setVisible(true) - this.question_id_to_insert_after = question_id_to_insert_after; - return; - // 拉取实训items - this.addEditingQuestion(Q_TYPE_SHIXUN, question_id_to_insert_after, { - shixun_id: 50 - }) - } - editQestion = (index) => { - if (this._checkIsEditing()) { - return; - } - this.editingId = `#question_${index}` - - this.setState( - (prevState) => ({ - exercise_questions : update(prevState.exercise_questions, {[index]: { isNew: {$set: true}}}) - }) - ) - } - onSort = (index, question_id, isUp) => { - if (this._checkIsEditing()) { - return; - } - const url = `/exercise_questions/${question_id}/up_down.json` - axios.post(url, { opr: isUp ? 'up' : 'down'}) - .then((response) => { - if (response.data.status == 0) { - // this.props.showNotification('移动成功') - this.fetchExercise() - } - }) - .catch(function (error) { - console.log(error); - }); - } - onSortDown = (index, question_id) => { - this.onSort(index, question_id, false) - } - onSortUp = (index, question_id) => { - this.onSort(index, question_id, true) - } - getInitScore = (question_type, question_id_to_insert_after) => { - /** - 1.每个题型的首个题目默认值规则如下: - 选择题:5分 01 - 判断题:2分 2 - 填空题:2分 3 - 简答题:10分 4 - 实训题:每个关卡5分 5 - */ - let init_question_score = 0; - if (question_type == 0 || question_type == 1) { - init_question_score = 5 - } else if (question_type == 2) { - init_question_score = 2 - } else if (question_type == 3) { - init_question_score = 2 - } else if (question_type == 4) { - init_question_score = 10 - } else if (question_type == 5) { - init_question_score = 5 - } - const _indexBefore = question_id_to_insert_after ? this.findIndexById(question_id_to_insert_after) : this.state.exercise_questions.length - 1 - for (let i = _indexBefore; i >= 0; i--) { - if(this.state.exercise_questions[i].question_type == question_type) { - init_question_score = this.state.exercise_questions[i].question_score - break; - } - } - return init_question_score; - } - addEditingQuestion = (question_type, question_id_to_insert_after, otherAttributes) => { - - let init_question_score = this.getInitScore(question_type, question_id_to_insert_after) - - let questionObj = { - question_type: question_type, // 需要这个通过类型判断 - init_question_score: init_question_score, - isNew: true, // 新建或编辑,用是否有id区分是新建还是编辑 - question_id_to_insert_after, - ...otherAttributes - } - const { exercise_questions } = this.state; - let new_exercise_questions = exercise_questions.slice(0) - let newIndex = new_exercise_questions.length; - - if (question_id_to_insert_after) { - const _indexBefore = this.findIndexById(question_id_to_insert_after) - new_exercise_questions.splice(_indexBefore + 1, 0, questionObj) - newIndex = _indexBefore + 1 - } else { - new_exercise_questions.push(questionObj) - } - this.editingId = `#question_${newIndex}` - this.setState({ exercise_questions: new_exercise_questions }, () => { - setTimeout(() => { - $(this.editingId).length && $("html").animate({ scrollTop: $(this.editingId).offset().top - 100}) - }, 500) - }) - } - findIndexById = (id) => { - const { exercise_questions } = this.state - for(let i = 0; i < exercise_questions.length; i++) { - if (exercise_questions[i].question_id == id) { - return i; - } - } - } - onQestionDelete = (question_id) => { - this.props.confirm({ - content: `确认要删除这个问题吗?`, - onOk: () => { - const url = `/exercise_questions/${question_id}.json` - axios.delete(url) - .then((response) => { - if (response.data.status == 0) { - this.props.showNotification('删除成功') - const { exercise_questions } = this.state - const index = this.findIndexById(question_id) - - this.setState( - (prevState) => ({ - exercise_questions : update(prevState.exercise_questions, {$splice: [[index, 1]]}) - }) - ) - } - }) - .catch(function (error) { - console.log(error); - }); - } - }) - } - addSuccess = () => { - this.editingId = null; - this.fetchExercise() - } - goToPreview = () => { - const exercise_id = this.props.match.params.Id - const courseId = this.props.match.params.coursesId - this.props.history.push(`/courses/${courseId}/exercises/${exercise_id}/student_exercise_list?tab=2`) - } - render() { - let { exercise_name, exercise_description, course_id, exercise_types, - exercise_questions, left_banner_id } = this.state; - // if (this.isEdit && !exercise_types) { - // return '' - // } - // const { getFieldDecorator } = this.props.form; - const { q_counts, q_scores, q_doubles, q_doubles_scores, q_judges, q_judges_scores, - q_mains, q_mains_scores, q_nulls, q_nulls_scores, q_shixuns, q_shixuns_scores, q_singles, q_singles_scores} = exercise_types; - const formItemLayout = { - labelCol: { - xs: { span: 24 }, - // sm: { span: 8 }, - sm: { span: 24 }, - }, - wrapperCol: { - xs: { span: 24 }, - // sm: { span: 16 }, - sm: { span: 24 }, - }, - }; - - const { current_user } = this.props - const isAdmin = this.props.isAdmin() - const courseId=this.props.match.params.coursesId; - const exercise_id = this.props.match.params.Id - - const isEdit = this.isEdit - const commonHandler = { - onQestionDelete: this.onQestionDelete, - addSuccess: this.addSuccess, - addQuestion: this.addQuestion, - onEditorCancel: this.onEditorCancel, - editQestion: this.editQestion, - onSortDown: this.onSortDown, - onSortUp: this.onSortUp, - displayCount: exercise_questions.length, - exercise_status: this.state.exercise_status, - exerciseIsPublish: this.state.exercise_status >= 2 - } - return( - <div className="newMain exerciseNew"> - <ShixunChooseModal - ref="shixunChooseModal" - chooseShixun={this.chooseShixun} - {...this.props} - singleChoose={true} - ></ShixunChooseModal> - <style>{` - .courseForm .formBlock { - padding: 20px 30px 30px 30px; - border-bottom: 1px solid #EDEDED; - margin-bottom: 0px; - background: #fff; - } - .exerciseNew .markdown-body { - max-width: 1128px; - } - `}</style> - <div className="edu-class-container edu-position courseForm"> - { current_user && <CBreadcrumb items={[ - { to: current_user.first_category_url, name: this.props.coursedata ? this.props.coursedata.name : ''}, - { to: `/courses/${courseId}/exercises/${left_banner_id}`, name: '试卷列表' }, - { name: this.isEdit ? '编辑试卷' : '新建试卷'} - ]}></CBreadcrumb> } - - <p className="clearfix mt20 mb20"> - <span className="fl font-24 color-grey-3">{this.isEdit ? "编辑" : "新建"}试卷</span> - <a href="javascript:void(0)" className="color-grey-6 fr font-16 mr2" - onClick={() => this.props.history.length == 1 ? this.props.history.push(`/courses/${courseId}/exercises/${left_banner_id}`): this.props.history.goBack()}> - 返回 - </a> - </p> - - {!this.state.editMode && <div className="padding20-30" style={{ background: '#fff'}}> - <div className="displayTitle font-16"> - <span>{exercise_name}</span> - <a className="fr mr6" onClick={() => { this.setState({editMode: true}) }} style={{ lineHeight: '32px'}}> - <Tooltip title="编辑"><i className="iconfont icon-bianjidaibeijing font-20 color-green"></i></Tooltip> - </a> - </div> - <div className="displayDescription color-grey-9" dangerouslySetInnerHTML={{__html: exercise_description}} - style={{whiteSpace: 'pre-wrap'}} - ></div> - - </div>} - {this.state.editMode && <Form {...formItemLayout} onSubmit={this.handleSubmit}> - <div className="formBlock" style={{paddingBottom: '2px',borderBottom:"none"}}> - <Form.Item - label="试卷标题" - required - className="topicTitle " - > - {/* {getFieldDecorator('subject', { - rules: [{ - required: true, message: '请输入标题', - }, { - max: 20, message: '最大限制为20个字符', - }], - })( */} - <Input placeholder="请输入试卷标题,最大限制60个字符" maxLength="60" className="input-100-40 mt5" value={exercise_name} onChange={this.exercise_name_change}/> - {/* )} */} - </Form.Item> - - <Form.Item - label=" 试卷须知" - > - {/* {getFieldDecorator('select_board_id', { - // initialValue: '3779', - })( */} - <TextArea placeholder="请在此输入本次试卷答题的相关说明,最大限制100个字符" className="mt5" style={{height:"120px"}} value={exercise_description} - onChange={this.exercise_description_change} - /> - {/* )} */} - </Form.Item> - <Form.Item> - {/* defalutSubmitbtn */} - - <a className="task-btn task-btn-orange fr mt4" style={{height: '30px', width: '70px'}} - onClick={this.onSaveExercise} - >保存</a> - - { this.isEdit && <a onClick={() => this.setState({editMode: false})} className="defalutCancelbtn fr mt4" - style={{height: '30px', width: '70px', fontSize: '14px', lineHeight: '30px', marginRight: '16px'}}>取消</a>} - {/* <Button type="primary" onClick={this.onSaveExercise} className="fr">保存</Button> */} - </Form.Item> - </div> - {/* <div className="clearfix mt30 mb30"> - <a className="defalutCancelbtn fl" onClick={() => {}}>取消</ a> - </div> */} - </Form>} - - - <p className="clearfix padding20-30 color-grey-9"> - <span className="fl"> - { !!q_singles && <span className="mr20">单选题{q_singles}题,共{q_singles_scores}分</span>} - { !!q_doubles && <span className="mr20">多选题{q_doubles}题,共{q_doubles_scores}分</span>} - { !!q_judges && <span className="mr20">判断题{q_judges}题,共{q_judges_scores}分</span>} - { !!q_nulls && <span className="mr20">填空题{q_nulls}题,共{q_nulls_scores}分</span>} - { !!q_mains && <span className="mr20">简答题{q_mains}题,共{q_mains_scores}分</span>} - { !!q_shixuns && <span className="mr20">实训题{q_shixuns}题,共{q_shixuns_scores}分</span> } - </span> - <span className="fr"> - { !!q_counts && - <span> - 合计 <span className="color-blue">{q_counts}</span> 题, - 共 <span className={`${q_scores > 100 ? 'color-red font-bd' : 'color-orange'}`}>{q_scores}</span> 分 - </span> - } - </span> - </p> - <div className="edu-back-white"> - { exercise_questions.map((item, index) => { - if (item.question_type == 0 || item.question_type == 1) { - if (item.isNew) { - return <SingleEditor {...this.props} {...item} index={index} {...commonHandler} ></SingleEditor> - } else { - return <SingleDisplay {...this.props} {...item} index={index} {...commonHandler} - displayCount={exercise_questions.length} - ></SingleDisplay> - } - } else if (item.question_type == 2) { - if (item.isNew) { - return <JudgeEditor {...this.props} {...item} index={index} {...commonHandler} ></JudgeEditor> - } else { - return <JudgeDisplay {...this.props} {...item} index={index} {...commonHandler} ></JudgeDisplay> - } - } else if (item.question_type == 3) { - if (item.isNew) { - return <NullEditor {...this.props} {...item} index={index} {...commonHandler} ></NullEditor> - } else { - return <NullDisplay {...this.props} {...item} index={index} {...commonHandler} ></NullDisplay> - } - } else if (item.question_type == 4) { - if (item.isNew) { - return <MainEditor {...this.props} {...item} index={index} {...commonHandler} ></MainEditor> - } else { - return <MainDisplay {...this.props} {...item} index={index} {...commonHandler} ></MainDisplay> - } - } else if (item.question_type == 5) { - if (item.isNew) { - return <ShixunEditor {...this.props} {...item} index={index} {...commonHandler} - chooseShixunSuccess={this.chooseShixunSuccess} - ></ShixunEditor> - } else { - return <ShixunDisplay {...this.props} {...item} index={index} {...commonHandler} ></ShixunDisplay> - } - } - return <div></div> - })} - - {!commonHandler.exerciseIsPublish && <div className="problemShow padding30"> - <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_SINGLE)}> - <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>选择题 - </ActionBtn> - <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_JUDGE)}> - <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>判断题 - </ActionBtn> - <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_NULL)}> - <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>填空题 - </ActionBtn> - <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_MAIN)}> - <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>简答题 - </ActionBtn> - <ActionBtn style="green" className="mr20" onClick={() => this.addShixun(null)}> - <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>实训题 - </ActionBtn> - - {exercise_id && <ActionBtn style="blue" className="fr" onClick={() => this.goToPreview()}> - {/* <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i> */} - 试卷预览 - </ActionBtn>} - </div>} - </div> - - </div> - </div> - ) - } -} -// RouteHOC() +import React,{ Component } from "react"; + +import { + Form, Input, InputNumber, Switch, Radio, + Slider, Button, Upload, Icon, Rate, Checkbox, message, + Row, Col, Select, Modal, Tooltip +} from 'antd'; +import { Q_TYPE_SINGLE, Q_TYPE_MULTI, Q_TYPE_JUDGE, Q_TYPE_NULL, Q_TYPE_MAIN, Q_TYPE_SHIXUN } from './new/common' +import TPMMDEditor from '../../tpm/challengesnew/TPMMDEditor'; +import axios from 'axios' +// import './board.css' +import "../common/formCommon.css" + +// import { RouteHOC } from './common.js' +import CBreadcrumb from '../common/CBreadcrumb' +import {getUrl, ActionBtn} from 'educoder'; + +import SingleEditor from './new/SingleEditor' +import SingleDisplay from './new/SingleDisplay' +import JudgeEditor from './new/JudgeEditor' +import JudgeDisplay from './new/JudgeDisplay' +import NullEditor from './new/NullEditor' +import NullDisplay from './new/NullDisplay' +import MainEditor from './new/MainEditor' +import MainDisplay from './new/MainDisplay' +import ShixunEditor from './new/ShixunEditor' +import ShixunDisplay from './new/ShixunDisplay' + +import ShixunChooseModal from '../coursesPublic/ShixunChooseModal' +import update from 'immutability-helper' +import './new/common.css' +import '../css/Courses.css' +const { TextArea } = Input; +const confirm = Modal.confirm; +const $ = window.$ +const { Option } = Select; + +class ExerciceNew extends Component{ + constructor(props){ + super(props); + + + this.state = { + exercise_questions: [], + exercise_name: '', + exercise_description: '', + exercise_types: {}, + editMode: !this.props.match.params.Id, + } + } + fetchExercise = () => { + const Id = this.props.match.params.Id + this.isEdit = !!Id + if (Id) { + const url = `/exercises/${Id}/edit.json` + axios.get(url) + .then((response) => { + if (response.data.status == 0) { + const { exercise, ...others } = response.data + this.setState({ + ...exercise, + ...others, + editMode: false + }) + } + }) + .catch(function (error) { + console.log(error); + }); + } else { + const courseId=this.props.match.params.coursesId; + + const newUrl = `/courses/${courseId}/exercises/new.json` + axios.get(newUrl) + .then((response) => { + if (response.data.status == 0) { + this.setState({ + ...response.data + }) + } + }) + .catch(function (error) { + console.log(error); + }); + } + } + componentDidMount = () => { + this.fetchExercise() + } + handleSubmit = (e) => { + + } + onSaveExercise = () => { + const { exercise_name, exercise_description } = this.state; + const exercise_id = this.props.match.params.Id + const courseId = this.props.match.params.coursesId + if (this.isEdit) { + const editUrl = `/exercises/${exercise_id}.json` + axios.put(editUrl, { + exercise_name, + exercise_description + }) + .then((response) => { + if (response.data.status == 0) { + this.setState({editMode: false}) + this.props.showNotification('试卷编辑成功') + } + }) + .catch(function (error) { + console.log(error); + }); + } else { + const url = `/courses/${courseId}/exercises.json` + axios.post(url, { + exercise_name, + exercise_description + }) + .then((response) => { + if (response.data.status == 0) { + this.setState({editMode: false}) + + this.props.showNotification('试卷新建成功') + const exercise_id = response.data.data.exercise_id; + this.isEdit = true; + + this.props.history.replace(`/courses/${courseId}/exercises/${exercise_id}/edit`); + + } + }) + .catch(function (error) { + console.log(error); + }); + } + } + exercise_name_change = (e) => { + this.setState({exercise_name: e.target.value}) + } + exercise_description_change = (e) => { + this.setState({exercise_description: e.target.value}) + } + // #问题的类型,0为单选题,1为多选题,2为判断题,3为填空题,4为主观题,5为实训题 + _checkIsEditing = () => { + if (this.editingId && $(this.editingId).length ) { + this.props.showNotification('请先保存或取消当前正在编辑的问题。') + $("html").animate({ scrollTop: $(this.editingId).offset().top - 100}) + return true + } + return false + } + onEditorCancel = () => { + this.editingId = null; + // 找到编辑或新建的item,新建就删掉item,编辑就isNew改为false + const { exercise_questions } = this.state + let index = -1; + for(let i = 0; i < exercise_questions.length; i++) { + if (exercise_questions[i].isNew == true) { + index = i; + break; + } + } + if (exercise_questions[index].question_id) { // 编辑 + this.setState( + (prevState) => ({ + exercise_questions : update(prevState.exercise_questions, {[index]: { isNew: {$set: false}}}) + // update(prevState.exercise_questions, {$splice: [[index, 1]]}) + }) + ) + } else { // 新建 + this.setState( + (prevState) => ({ + exercise_questions : update(prevState.exercise_questions, {$splice: [[index, 1]]}) + }) + ) + } + } + addQuestion = (question_id_to_insert_after, type) => { + if (!this.isEdit) { + this.props.showNotification('请先输入试卷标题,并保存试卷') + return; + } + if (this._checkIsEditing()) { + return; + } + if (type == Q_TYPE_SHIXUN) { + this.addShixun(question_id_to_insert_after) + } else { + this.addEditingQuestion(type, question_id_to_insert_after) + } + } + chooseShixun = (array) => { + this.addEditingQuestion(Q_TYPE_SHIXUN, this.question_id_to_insert_after, { + shixun_id: array[0] + }) + } + chooseShixunSuccess = () => { + this.refs.shixunChooseModal.setVisible(false) + } + addShixun = (question_id_to_insert_after) => { + if (!this.isEdit) { + this.props.showNotification('请先输入试卷标题,并保存试卷') + return; + } + // TODO 弹框选择实训 + if (this._checkIsEditing()) { + return; + } + this.refs.shixunChooseModal.setVisible(true) + this.question_id_to_insert_after = question_id_to_insert_after; + return; + // 拉取实训items + this.addEditingQuestion(Q_TYPE_SHIXUN, question_id_to_insert_after, { + shixun_id: 50 + }) + } + editQestion = (index) => { + if (this._checkIsEditing()) { + return; + } + this.editingId = `#question_${index}` + + this.setState( + (prevState) => ({ + exercise_questions : update(prevState.exercise_questions, {[index]: { isNew: {$set: true}}}) + }) + ) + } + onSort = (index, question_id, isUp) => { + if (this._checkIsEditing()) { + return; + } + const url = `/exercise_questions/${question_id}/up_down.json` + axios.post(url, { opr: isUp ? 'up' : 'down'}) + .then((response) => { + if (response.data.status == 0) { + // this.props.showNotification('移动成功') + this.fetchExercise() + } + }) + .catch(function (error) { + console.log(error); + }); + } + onSortDown = (index, question_id) => { + this.onSort(index, question_id, false) + } + onSortUp = (index, question_id) => { + this.onSort(index, question_id, true) + } + getInitScore = (question_type, question_id_to_insert_after) => { + /** + 1.每个题型的首个题目默认值规则如下: + 选择题:5分 01 + 判断题:2分 2 + 填空题:2分 3 + 简答题:10分 4 + 实训题:每个关卡5分 5 + */ + let init_question_score = 0; + if (question_type == 0 || question_type == 1) { + init_question_score = 5 + } else if (question_type == 2) { + init_question_score = 2 + } else if (question_type == 3) { + init_question_score = 2 + } else if (question_type == 4) { + init_question_score = 10 + } else if (question_type == 5) { + init_question_score = 5 + } + const _indexBefore = question_id_to_insert_after ? this.findIndexById(question_id_to_insert_after) : this.state.exercise_questions.length - 1 + for (let i = _indexBefore; i >= 0; i--) { + if(this.state.exercise_questions[i].question_type == question_type) { + init_question_score = this.state.exercise_questions[i].question_score + break; + } + } + return init_question_score; + } + addEditingQuestion = (question_type, question_id_to_insert_after, otherAttributes) => { + + let init_question_score = this.getInitScore(question_type, question_id_to_insert_after) + + let questionObj = { + question_type: question_type, // 需要这个通过类型判断 + init_question_score: init_question_score, + isNew: true, // 新建或编辑,用是否有id区分是新建还是编辑 + question_id_to_insert_after, + ...otherAttributes + } + const { exercise_questions } = this.state; + let new_exercise_questions = exercise_questions.slice(0) + let newIndex = new_exercise_questions.length; + + if (question_id_to_insert_after) { + const _indexBefore = this.findIndexById(question_id_to_insert_after) + new_exercise_questions.splice(_indexBefore + 1, 0, questionObj) + newIndex = _indexBefore + 1 + } else { + new_exercise_questions.push(questionObj) + } + this.editingId = `#question_${newIndex}` + this.setState({ exercise_questions: new_exercise_questions }, () => { + setTimeout(() => { + $(this.editingId).length && $("html").animate({ scrollTop: $(this.editingId).offset().top - 100}) + }, 500) + }) + } + findIndexById = (id) => { + const { exercise_questions } = this.state + for(let i = 0; i < exercise_questions.length; i++) { + if (exercise_questions[i].question_id == id) { + return i; + } + } + } + onQestionDelete = (question_id) => { + this.props.confirm({ + content: `确认要删除这个问题吗?`, + onOk: () => { + const url = `/exercise_questions/${question_id}.json` + axios.delete(url) + .then((response) => { + if (response.data.status == 0) { + this.props.showNotification('删除成功') + const { exercise_questions } = this.state + const index = this.findIndexById(question_id) + + this.setState( + (prevState) => ({ + exercise_questions : update(prevState.exercise_questions, {$splice: [[index, 1]]}) + }) + ) + } + }) + .catch(function (error) { + console.log(error); + }); + } + }) + } + addSuccess = () => { + this.editingId = null; + this.fetchExercise() + } + goToPreview = () => { + const exercise_id = this.props.match.params.Id + const courseId = this.props.match.params.coursesId + this.props.history.push(`/courses/${courseId}/exercises/${exercise_id}/student_exercise_list?tab=2`) + } + render() { + let { exercise_name, exercise_description, course_id, exercise_types, + exercise_questions, left_banner_id } = this.state; + // if (this.isEdit && !exercise_types) { + // return '' + // } + // const { getFieldDecorator } = this.props.form; + const { q_counts, q_scores, q_doubles, q_doubles_scores, q_judges, q_judges_scores, + q_mains, q_mains_scores, q_nulls, q_nulls_scores, q_shixuns, q_shixuns_scores, q_singles, q_singles_scores} = exercise_types; + const formItemLayout = { + labelCol: { + xs: { span: 24 }, + // sm: { span: 8 }, + sm: { span: 24 }, + }, + wrapperCol: { + xs: { span: 24 }, + // sm: { span: 16 }, + sm: { span: 24 }, + }, + }; + + const { current_user } = this.props + const isAdmin = this.props.isAdmin() + const courseId=this.props.match.params.coursesId; + const exercise_id = this.props.match.params.Id + + const isEdit = this.isEdit + const commonHandler = { + onQestionDelete: this.onQestionDelete, + addSuccess: this.addSuccess, + addQuestion: this.addQuestion, + onEditorCancel: this.onEditorCancel, + editQestion: this.editQestion, + onSortDown: this.onSortDown, + onSortUp: this.onSortUp, + displayCount: exercise_questions.length, + exercise_status: this.state.exercise_status, + exerciseIsPublish: this.state.exercise_status >= 2 + } + return( + <div className="newMain exerciseNew"> + <ShixunChooseModal + ref="shixunChooseModal" + chooseShixun={this.chooseShixun} + {...this.props} + singleChoose={true} + ></ShixunChooseModal> + <style>{` + .courseForm .formBlock { + padding: 20px 30px 30px 30px; + border-bottom: 1px solid #EDEDED; + margin-bottom: 0px; + background: #fff; + } + .exerciseNew .markdown-body { + max-width: 1128px; + } + `}</style> + <div className="edu-class-container edu-position courseForm"> + { current_user && <CBreadcrumb items={[ + { to: current_user&¤t_user.first_category_url, name: this.props.coursedata ? this.props.coursedata.name : ''}, + { to: `/courses/${courseId}/exercises/${left_banner_id}`, name: '试卷列表' }, + { name: this.isEdit ? '编辑试卷' : '新建试卷'} + ]}></CBreadcrumb> } + + <p className="clearfix mt20 mb20"> + <span className="fl font-24 color-grey-3">{this.isEdit ? "编辑" : "新建"}试卷</span> + <a href="javascript:void(0)" className="color-grey-6 fr font-16 mr2" + onClick={() => this.props.history.length == 1 ? this.props.history.push(`/courses/${courseId}/exercises/${left_banner_id}`): this.props.history.goBack()}> + 返回 + </a> + </p> + + {!this.state.editMode && <div className="padding20-30" style={{ background: '#fff'}}> + <div className="displayTitle font-16"> + <span>{exercise_name}</span> + <a className="fr mr6" onClick={() => { this.setState({editMode: true}) }} style={{ lineHeight: '32px'}}> + <Tooltip title="编辑"><i className="iconfont icon-bianjidaibeijing font-20 color-green"></i></Tooltip> + </a> + </div> + <div className="displayDescription color-grey-9" dangerouslySetInnerHTML={{__html: exercise_description}} + style={{whiteSpace: 'pre-wrap'}} + ></div> + + </div>} + {this.state.editMode && <Form {...formItemLayout} onSubmit={this.handleSubmit}> + <div className="formBlock" style={{paddingBottom: '2px',borderBottom:"none"}}> + <Form.Item + label="试卷标题" + required + className="topicTitle " + > + {/* {getFieldDecorator('subject', { + rules: [{ + required: true, message: '请输入标题', + }, { + max: 20, message: '最大限制为20个字符', + }], + })( */} + <Input placeholder="请输入试卷标题,最大限制60个字符" maxLength="60" className="input-100-40 mt5" value={exercise_name} onChange={this.exercise_name_change}/> + {/* )} */} + </Form.Item> + + <Form.Item + label=" 试卷须知" + > + {/* {getFieldDecorator('select_board_id', { + // initialValue: '3779', + })( */} + <TextArea placeholder="请在此输入本次试卷答题的相关说明,最大限制100个字符" className="mt5" style={{height:"120px"}} value={exercise_description} + onChange={this.exercise_description_change} + /> + {/* )} */} + </Form.Item> + <Form.Item> + {/* defalutSubmitbtn */} + + <a className="task-btn task-btn-orange fr mt4" style={{height: '30px', width: '70px'}} + onClick={this.onSaveExercise} + >保存</a> + + { this.isEdit && <a onClick={() => this.setState({editMode: false})} className="defalutCancelbtn fr mt4" + style={{height: '30px', width: '70px', fontSize: '14px', lineHeight: '30px', marginRight: '16px'}}>取消</a>} + {/* <Button type="primary" onClick={this.onSaveExercise} className="fr">保存</Button> */} + </Form.Item> + </div> + {/* <div className="clearfix mt30 mb30"> + <a className="defalutCancelbtn fl" onClick={() => {}}>取消</ a> + </div> */} + </Form>} + + + <p className="clearfix padding20-30 color-grey-9"> + <span className="fl"> + { !!q_singles && <span className="mr20">单选题{q_singles}题,共{q_singles_scores}分</span>} + { !!q_doubles && <span className="mr20">多选题{q_doubles}题,共{q_doubles_scores}分</span>} + { !!q_judges && <span className="mr20">判断题{q_judges}题,共{q_judges_scores}分</span>} + { !!q_nulls && <span className="mr20">填空题{q_nulls}题,共{q_nulls_scores}分</span>} + { !!q_mains && <span className="mr20">简答题{q_mains}题,共{q_mains_scores}分</span>} + { !!q_shixuns && <span className="mr20">实训题{q_shixuns}题,共{q_shixuns_scores}分</span> } + </span> + <span className="fr"> + { !!q_counts && + <span> + 合计 <span className="color-blue">{q_counts}</span> 题, + 共 <span className={`${q_scores > 100 ? 'color-red font-bd' : 'color-orange'}`}>{q_scores}</span> 分 + </span> + } + </span> + </p> + <div className="edu-back-white"> + { exercise_questions.map((item, index) => { + if (item.question_type == 0 || item.question_type == 1) { + if (item.isNew) { + return <SingleEditor {...this.props} {...item} index={index} {...commonHandler} ></SingleEditor> + } else { + return <SingleDisplay {...this.props} {...item} index={index} {...commonHandler} + displayCount={exercise_questions.length} + ></SingleDisplay> + } + } else if (item.question_type == 2) { + if (item.isNew) { + return <JudgeEditor {...this.props} {...item} index={index} {...commonHandler} ></JudgeEditor> + } else { + return <JudgeDisplay {...this.props} {...item} index={index} {...commonHandler} ></JudgeDisplay> + } + } else if (item.question_type == 3) { + if (item.isNew) { + return <NullEditor {...this.props} {...item} index={index} {...commonHandler} ></NullEditor> + } else { + return <NullDisplay {...this.props} {...item} index={index} {...commonHandler} ></NullDisplay> + } + } else if (item.question_type == 4) { + if (item.isNew) { + return <MainEditor {...this.props} {...item} index={index} {...commonHandler} ></MainEditor> + } else { + return <MainDisplay {...this.props} {...item} index={index} {...commonHandler} ></MainDisplay> + } + } else if (item.question_type == 5) { + if (item.isNew) { + return <ShixunEditor {...this.props} {...item} index={index} {...commonHandler} + chooseShixunSuccess={this.chooseShixunSuccess} + ></ShixunEditor> + } else { + return <ShixunDisplay {...this.props} {...item} index={index} {...commonHandler} ></ShixunDisplay> + } + } + return <div></div> + })} + + {!commonHandler.exerciseIsPublish && <div className="problemShow padding30"> + <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_SINGLE)}> + <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>选择题 + </ActionBtn> + <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_JUDGE)}> + <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>判断题 + </ActionBtn> + <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_NULL)}> + <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>填空题 + </ActionBtn> + <ActionBtn style="green" className="mr20" onClick={() => this.addQuestion(null, Q_TYPE_MAIN)}> + <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>简答题 + </ActionBtn> + <ActionBtn style="green" className="mr20" onClick={() => this.addShixun(null)}> + <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i>实训题 + </ActionBtn> + + {exercise_id && <ActionBtn style="blue" className="fr" onClick={() => this.goToPreview()}> + {/* <i className="iconfont icon-tianjiafangda color-white font-14 mr5" style={{ marginTop: '-1px', display: 'inline-block'}}></i> */} + 试卷预览 + </ActionBtn>} + </div>} + </div> + + </div> + </div> + ) + } +} +// RouteHOC() export default (ExerciceNew); \ No newline at end of file diff --git a/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js b/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js index c0e8fe185..b24277a2c 100644 --- a/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js +++ b/public/react/src/modules/courses/graduation/topics/GraduateTopicDetail.js @@ -105,7 +105,7 @@ class GraduateTopicDetail extends Component{ <div className="newMain"> <div className="educontent mt10 mb50"> <p className="clearfix mb15 lineh-20"> - <WordsBtn style="grey" className="fl" to={current_user.first_category_url}>{tableData && tableData.course_name}</WordsBtn> + <WordsBtn style="grey" className="fl" to={current_user&¤t_user.first_category_url}>{tableData && tableData.course_name}</WordsBtn> <span className="color-grey-9 fl ml3 mr3">></span> <WordsBtn style="grey" className="fl" to={`/courses/${tableData.course_id}/graduation_topics/${tableData.graduation_id}`}>{tableData.graduation_name}</WordsBtn> <span className="color-grey-9 fl ml3 mr3">></span> diff --git a/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js b/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js index 43a0e31f5..8c46bfa0e 100644 --- a/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js +++ b/public/react/src/modules/courses/graduation/topics/GraduateTopicNew.js @@ -323,7 +323,7 @@ class GraduateTopicNew extends Component{ `}</style> <div className="edu-class-container edu-position courseForm"> <p className="clearfix mb20 mt10"> - <WordsBtn style="grey" className="fl" to={current_user.first_category_url}>{course_name}</WordsBtn> + <WordsBtn style="grey" className="fl" to={current_user&¤t_user.first_category_url}>{course_name}</WordsBtn> <span className="color-grey-9 fl ml3 mr3">></span> <WordsBtn style="grey" className="fl" to={`/courses/${coursesId}/graduation_topics/${left_banner_id}`}>{left_banner_name}</WordsBtn> <span className="color-grey-9 fl ml3 mr3">></span>