parent
ac67cdafad
commit
0ed5b1d79f
@ -1,3 +1,3 @@
|
||||
json.extract! ec_training_objective, :id, :content
|
||||
|
||||
json.ec_training_items ec_training_objective.ec_training_subitems, partial: 'ec_training_subitem', as: :ec_training_subitem
|
||||
json.ec_training_items ec_training_objective.ec_training_subitems, partial: '/ecs/ec_training_objectives/shared/ec_training_subitem', as: :ec_training_subitem
|
||||
|
@ -1 +1 @@
|
||||
json.partial! 'shared/ec_training_objective', ec_training_objective: @training_objective
|
||||
json.partial! '/ecs/ec_training_objectives/shared/ec_training_objective', ec_training_objective: @training_objective
|
||||
|
@ -0,0 +1,13 @@
|
||||
json.extract! @year, :id, :year
|
||||
|
||||
major = @year.ec_major_school
|
||||
json.major_id major.id
|
||||
json.major_name major.name
|
||||
json.major_code major.code
|
||||
|
||||
school = major.school
|
||||
json.school_id school.id
|
||||
json.school_name school.name
|
||||
|
||||
can_manager = major.manager?(current_user) || school.manager?(current_user) || current_user.admin_or_business?
|
||||
json.can_manager can_manager
|
@ -0,0 +1,264 @@
|
||||
import React from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Spin, Button, Input, Divider, Icon, Tooltip, Form, message } from 'antd';
|
||||
import axios from 'axios';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
class TrainingObjective extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
loading: true,
|
||||
contentEditState: false,
|
||||
itemsEditState: false,
|
||||
submitState: false,
|
||||
validateState: false,
|
||||
itemSubmitState: false,
|
||||
itemValidateState: false,
|
||||
|
||||
objective: {},
|
||||
editContent: '',
|
||||
trainingSubitems: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getData = () => {
|
||||
let { yearId } = this.props;
|
||||
|
||||
axios.get(`/ec_years/${yearId}/ec_training_objectives.json`).then(res => {
|
||||
if(res.status === 200){
|
||||
this.setState({
|
||||
objective: res.data,
|
||||
editContent: res.data.content,
|
||||
trainingSubitems: res.data.ec_training_items,
|
||||
loading: false
|
||||
})
|
||||
}
|
||||
}).catch(e => console.log(e))
|
||||
}
|
||||
|
||||
saveContentEdit = () => {
|
||||
let { editContent } = this.state;
|
||||
this.setState({ validateState: editContent.length === 0 });
|
||||
if(editContent.length === 0){ return; }
|
||||
|
||||
this.setState(
|
||||
{ submitState: true },
|
||||
() => {
|
||||
this.updateTrainingObjective(
|
||||
{ content: editContent },
|
||||
() => {
|
||||
this.setState({ submitState: false, contentEditState: false });
|
||||
this.getData();
|
||||
},
|
||||
_e => {
|
||||
this.setState({ submitState: false })
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
cancelContentEdit = () => {
|
||||
this.setState({ editContent: this.state.objective.content, contentEditState: false });
|
||||
}
|
||||
|
||||
editItemsContent = () => {
|
||||
let { trainingSubitems } = this.state;
|
||||
if(!trainingSubitems || trainingSubitems.length === 0){
|
||||
trainingSubitems = [{ id: null, content: null }]
|
||||
}
|
||||
this.setState({ trainingSubitems: trainingSubitems, itemsEditState: true });
|
||||
}
|
||||
|
||||
addItemColumn = (index) => {
|
||||
let { trainingSubitems } = this.state;
|
||||
trainingSubitems.splice(index, 0, { id: null, content: null });
|
||||
this.setState({ trainingSubitems })
|
||||
}
|
||||
|
||||
removeItemColumn = (index) => {
|
||||
let { trainingSubitems } = this.state;
|
||||
trainingSubitems.splice(index, 1);
|
||||
this.setState({ trainingSubitems })
|
||||
}
|
||||
|
||||
onItemContentChange = (e, index) => {
|
||||
let { trainingSubitems } = this.state;
|
||||
trainingSubitems[index].content = e.target.value;
|
||||
|
||||
this.setState({ trainingSubitems: trainingSubitems });
|
||||
}
|
||||
|
||||
saveItemsContentEdit = () => {
|
||||
let { objective, trainingSubitems } = this.state;
|
||||
|
||||
let errorItem = trainingSubitems.find(item => !item.content || item.content.length === 0);
|
||||
this.setState({ itemValidateState: !!errorItem });
|
||||
|
||||
if(errorItem){ return }
|
||||
|
||||
this.setState(
|
||||
{ itemSubmitState: true },
|
||||
() => {
|
||||
this.updateTrainingObjective(
|
||||
{ content: objective.content, training_subitems: trainingSubitems },
|
||||
() => {
|
||||
this.setState({ itemSubmitState: false, itemsEditState: false });
|
||||
this.getData();
|
||||
},
|
||||
_e => {
|
||||
this.setState({ itemSubmitState: false })
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
cancelItemsContentEdit = () => {
|
||||
this.setState({ trainingSubitems: this.state.objective.ec_training_items, itemsEditState: false, itemValidateState: false });
|
||||
}
|
||||
|
||||
updateTrainingObjective = (data, success, fail) => {
|
||||
let { yearId } = this.props;
|
||||
let url = `/ec_years/${yearId}/ec_training_objectives.json`;
|
||||
|
||||
axios.post(url, data).then(res => {
|
||||
if(res){
|
||||
message.success('操作成功');
|
||||
success();
|
||||
}
|
||||
}).catch(e => {
|
||||
console.log(e);
|
||||
fail(e);
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let { can_manager } = this.props.year;
|
||||
let { loading, contentEditState, itemsEditState, objective, editContent, trainingSubitems, validateState, itemValidateState, itemSubmitState, submitState } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spin spinning={loading} size='large' style={{ marginTop: '15%' }}>
|
||||
<div className="educontent ec-training-objective-page">
|
||||
<div className="ec-head">
|
||||
<div className="ec-head-left">
|
||||
<div className="ec-head-label">培养目标</div>
|
||||
<div className="ec-head-tip">
|
||||
<span>请结合本专业特色修改培养目标文字描述及目标分解查看详情</span>
|
||||
<Link to="/forums/3529" target="_blank" className="link ml10">查看详情</Link>
|
||||
</div>
|
||||
</div>
|
||||
<a href={`/api/ec_years/${this.props.yearId}/ec_training_objectives.xlsx`} target="_blank" className="ant-btn ant-btn-primary color-white">导出培养目标</a>
|
||||
</div>
|
||||
|
||||
<Divider/>
|
||||
|
||||
<div className="training-objective-body">
|
||||
{
|
||||
can_manager && contentEditState ? (
|
||||
<div className="training-objective-content block">
|
||||
<div>
|
||||
<Form.Item label={false} validateStatus={validateState && (!editContent || editContent.length === 0) ? 'error' : ''}>
|
||||
<Input.TextArea rows={6} value={editContent} onChange={e => this.setState({ editContent: e.target.value })} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div className="training-objective-content-form">
|
||||
<Button type="primary" loading={submitState} onClick={this.saveContentEdit}>保存</Button>
|
||||
<Button loading={submitState} onClick={this.cancelContentEdit}>取消</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="training-objective-content">
|
||||
<div className="training-objective-content-text">{ objective.content }</div>
|
||||
{
|
||||
can_manager && (
|
||||
<div className="training-objective-content-edit">
|
||||
<Tooltip title="编辑">
|
||||
<Icon type="edit" theme="filled" className="edit-action" onClick={() => this.setState({ contentEditState: true })} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className="training-objective-items">
|
||||
<div className="training-objective-items-head">
|
||||
<div className="no-column">分项</div>
|
||||
<div className="item-content-column">目标分解详情</div>
|
||||
<div className="operation-column">
|
||||
{
|
||||
itemsEditState || (
|
||||
<Tooltip title="编辑">
|
||||
<Icon type="edit" theme="filled" className="edit-action" onClick={this.editItemsContent} />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="training-objective-items-body">
|
||||
{
|
||||
can_manager && itemsEditState ? (
|
||||
<div>
|
||||
{
|
||||
trainingSubitems && trainingSubitems.map((item, index) => {
|
||||
return (
|
||||
<div className="training-objective-items-body-item" key={index}>
|
||||
<div className="no-column">{index + 1}</div>
|
||||
<div className="item-content-column">
|
||||
<Form.Item label={false} validateStatus={itemValidateState && (!item.content || item.content.length === 0) ? 'error' : ''}>
|
||||
<Input.TextArea rows={2} value={item.content} onChange={e => this.onItemContentChange(e, index)} />
|
||||
</Form.Item>
|
||||
<div className="item-column-operation">
|
||||
{ index !== 0 && <Icon type="delete" onClick={() => this.removeItemColumn(index)} /> }
|
||||
|
||||
<Icon type="plus-circle" onClick={() => this.addItemColumn(index + 1)} style={{ color: '#29BD8B' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
<div className="training-objective-content-form">
|
||||
<Button type="primary" loading={itemSubmitState} onClick={this.saveItemsContentEdit}>保存</Button>
|
||||
<Button disabled={itemSubmitState} onClick={this.cancelItemsContentEdit}>取消</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
objective.ec_training_items && objective.ec_training_items.map((item, index) => {
|
||||
return (
|
||||
<div className="training-objective-items-body-item" key={index}>
|
||||
<div className="no-column">{ index + 1 }</div>
|
||||
<div className="item-content-column">{ item.content }</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
TrainingObjective.propTypes = {
|
||||
schoolId: PropTypes.string,
|
||||
majorId: PropTypes.string,
|
||||
yearId: PropTypes.string,
|
||||
}
|
||||
|
||||
export default TrainingObjective
|
@ -0,0 +1,99 @@
|
||||
.ec-training-objective-page {
|
||||
background: #ffffff;
|
||||
|
||||
.training-objective {
|
||||
&-body {
|
||||
margin-top: -24px;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
padding: 20px 30px;
|
||||
line-height: 2.0;
|
||||
|
||||
&.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-edit {
|
||||
margin-left: 20px;
|
||||
|
||||
& > i {
|
||||
color: #29BD8B;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&-form {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-items {
|
||||
&-head {
|
||||
padding: 15px 30px;
|
||||
display: flex;
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
&-body {
|
||||
margin: 0 30px;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 10px 0px;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-column {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-content-column {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
|
||||
.ant-form-item {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-column-operation {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 80px;
|
||||
|
||||
& > i {
|
||||
margin: 15px 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i.edit-action {
|
||||
color: #29BD8B;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route, Link } from 'react-router-dom';
|
||||
import { Steps, Breadcrumb } from 'antd';
|
||||
import axios from 'axios';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
import CustomLoadable from "../../../CustomLoadable";
|
||||
|
||||
const { Step } = Steps;
|
||||
|
||||
const TrainingObjective = CustomLoadable(() => import('./TrainingObjective/index'))
|
||||
|
||||
const steps = ["培养目标", "毕业要求", "培养目标VS毕业要求", "毕业要求VS通用标准", "学生", "课程体系", "课程体系VS毕业要求", "达成度评价结果"];
|
||||
const stepTypes = ["training_objectives", "graduation_requirement", "requirement_vs_objective", "requirement_vs_standard", "students", "courses", "requirement_vs_courses", "reach_calculation_info"];
|
||||
|
||||
class EcSetting extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
schoolId: null,
|
||||
majorId: props.match.params.majorId,
|
||||
yearId: props.match.params.yearId,
|
||||
year: null,
|
||||
|
||||
stepIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setupStep();
|
||||
this.getYearDetail();
|
||||
}
|
||||
|
||||
getYearDetail = () => {
|
||||
let { majorId, yearId } = this.state;
|
||||
axios.get(`/ec_major_schools/${majorId}/ec_years/${yearId}.json`).then(res => {
|
||||
if(res){
|
||||
this.setState({ year: res.data, schoolId: res.data.school_id })
|
||||
}
|
||||
}).catch(e => console.log(e))
|
||||
}
|
||||
|
||||
onStepChange = (stepIndex) => {
|
||||
let { majorId, yearId } = this.state;
|
||||
let type = stepTypes[stepIndex];
|
||||
|
||||
this.setState({ stepIndex: stepIndex });
|
||||
this.props.history.push(`/ecs/major_schools/${majorId}/years/${yearId}/${type}`);
|
||||
}
|
||||
|
||||
setupStep = () => {
|
||||
let type = this.props.match.params.type;
|
||||
|
||||
let stepIndex = stepTypes.indexOf(type);
|
||||
this.setState({ stepIndex: stepIndex });
|
||||
}
|
||||
|
||||
render() {
|
||||
let { year, schoolId, majorId, yearId } = this.state;
|
||||
let { stepIndex } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="ec-page">
|
||||
<div className="educontent ec-breadcrumb">
|
||||
<Breadcrumb separator=">">
|
||||
<Breadcrumb.Item key="department">
|
||||
<Link to={`/ecs/department?school_id=${schoolId}`}>{ year && year.school_name }</Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item key="major-school">
|
||||
<Link to={`/ecs/major_schools/${majorId}`}>{ year && year.major_name }</Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item key="year">{year && year.year}届</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
|
||||
<div className="educontent ec-steps">
|
||||
<Steps type="navigation"
|
||||
size='small'
|
||||
className="ec-steps-box"
|
||||
current={stepIndex}
|
||||
onChange={this.onStepChange}>
|
||||
|
||||
{
|
||||
steps.map((title, index) => {
|
||||
return (
|
||||
<Step subTitle={title} status={index === stepIndex ? 'process' : 'wait'} key={index}/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Steps>
|
||||
</div>
|
||||
|
||||
{
|
||||
year && (
|
||||
<Switch>
|
||||
<Route extra path='/ecs/major_schools/:majorId/years/:yearId/training_objectives'
|
||||
render={ (props) => (<TrainingObjective {...this.props} {...props} {...this.state} />) }></Route>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default EcSetting
|
@ -0,0 +1,62 @@
|
||||
.ec-page {
|
||||
margin-bottom: 50px;
|
||||
|
||||
.ec-breadcrumb {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ec-steps {
|
||||
&-box {
|
||||
margin-bottom: 10px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
|
||||
.ant-steps-item {
|
||||
flex: unset;
|
||||
|
||||
&-container {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&.ant-steps-item-active {
|
||||
.ant-steps-item-subtitle {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ec-head {
|
||||
margin-bottom: -24px;
|
||||
padding: 20px 30px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
|
||||
&-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&-tip {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue