diff --git a/app/controllers/ecs/ec_graduation_requirements_controller.rb b/app/controllers/ecs/ec_graduation_requirements_controller.rb index 95dafdb3c..0647a7914 100644 --- a/app/controllers/ecs/ec_graduation_requirements_controller.rb +++ b/app/controllers/ecs/ec_graduation_requirements_controller.rb @@ -15,19 +15,31 @@ class Ecs::EcGraduationRequirementsController < Ecs::BaseController end def create - graduation_requirement = current_year.graduation_requirements.new + graduation_requirement = current_year.ec_graduation_requirements.new @graduation_requirement = Ecs::SaveGraduationRequirementeService.call(graduation_requirement, create_params) render 'show' end def update - graduation_requirement = current_year.graduation_requirements.find(params[:id]) - @graduation_requirement = Ecs::SaveGraduationRequirementeService.call(graduation_requirement, update_params) + @graduation_requirement = Ecs::SaveGraduationRequirementeService.call(current_graduation_requirement, update_params) render 'show' end + def destroy + ActiveRecord::Base.transaction do + current_graduation_requirement.destroy! + current_year.ec_graduation_requirements.where('position > ?', current_graduation_requirement.position) + .update_all('position = position - 1') + end + render_ok + end + private + def current_graduation_requirement + @_current_graduation_requirement ||= current_year.ec_graduation_requirements.find(params[:id]) + end + def create_params params.permit(:position, :content, graduation_subitems: [:content]) end diff --git a/app/models/ec_graduation_requirement.rb b/app/models/ec_graduation_requirement.rb index d0f4195d0..f9c65e28e 100644 --- a/app/models/ec_graduation_requirement.rb +++ b/app/models/ec_graduation_requirement.rb @@ -1,4 +1,6 @@ class EcGraduationRequirement < ApplicationRecord + default_scope { order(position: :asc) } + belongs_to :ec_year has_many :ec_graduation_subitems, dependent: :destroy @@ -7,5 +9,5 @@ class EcGraduationRequirement < ApplicationRecord validates :position, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :content, presence: true - default_scope { order(position: :asc) } + accepts_nested_attributes_for :ec_graduation_subitems, allow_destroy: true end diff --git a/app/views/ecs/ec_graduation_requirements/index.json.jbuilder b/app/views/ecs/ec_graduation_requirements/index.json.jbuilder index 6fbbf249b..ffb83ed23 100644 --- a/app/views/ecs/ec_graduation_requirements/index.json.jbuilder +++ b/app/views/ecs/ec_graduation_requirements/index.json.jbuilder @@ -1,3 +1,3 @@ json.count @graduation_requirements.size -json.graduation_requirements @graduation_requirements, partial: 'shared/ec_graduation_requirement', as: :ec_graduation_requirement +json.graduation_requirements @graduation_requirements, partial: '/ecs/ec_graduation_requirements/shared/ec_graduation_requirement', as: :ec_graduation_requirement diff --git a/app/views/ecs/ec_graduation_requirements/show.json.jbuilder b/app/views/ecs/ec_graduation_requirements/show.json.jbuilder index 562c255a9..d3a8db1fc 100644 --- a/app/views/ecs/ec_graduation_requirements/show.json.jbuilder +++ b/app/views/ecs/ec_graduation_requirements/show.json.jbuilder @@ -1,2 +1,2 @@ -json.partial! 'shared/ec_graduation_requirement', ec_graduation_requirement: @graduation_requirement +json.partial! 'ecs/ec_graduation_requirements/shared/ec_graduation_requirement', ec_graduation_requirement: @graduation_requirement diff --git a/config/routes.rb b/config/routes.rb index 8098d03c9..f5c1a54d3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -715,7 +715,7 @@ Rails.application.routes.draw do resources :ec_years, only: [] do resource :ec_training_objectives, only: [:show, :create] - resources :ec_graduation_requirements, only: [:index, :create] + resources :ec_graduation_requirements, only: [:index, :create, :update, :destroy] resource :requirement_support_objectives, only: [:show, :create, :destroy] resource :subitem_support_standards, only: [:show, :create, :destroy] resource :students, only: [:show, :destroy] do diff --git a/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.js b/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.js index ea46b2604..4ee387a73 100644 --- a/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.js +++ b/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.js @@ -1,18 +1,27 @@ 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 { Spin, Button, Input, Divider, Icon, Tooltip, Form, message, Modal } from 'antd'; import axios from 'axios'; +import _ from 'lodash' import './index.scss'; +const { confirm } = Modal; + class GraduationRequirement extends React.Component { constructor (props) { super(props); this.state = { loading: true, + editIndex: null, + addState: false, + submitState: false, + validateState: false, + currentEditReq: {}, + newRequirement: {}, graduationRequirements: [] } } @@ -24,19 +33,193 @@ class GraduationRequirement extends React.Component { getData = () => { let { yearId } = this.props; + this.setState({ loading: true }); axios.get(`/ec_years/${yearId}/ec_graduation_requirements.json`).then(res => { if(res.status === 200){ this.setState({ - graduationRequirements: res.data.ec_graduation_requirements, + graduationRequirements: res.data.graduation_requirements, loading: false }) } }).catch(e => console.log(e)) } + showDeleteConfirm = (id) => { + if(this.state.editIndex !== null || this.state.addState){ + message.error('请先保存其它内容'); + return + } + confirm({ + title: '确认删除该毕业要求?', + okText: '确认', + cancelText: '取消', + onOk: () => { + this.deleteRequirement(id); + }, + onCancel() {}, + }); + } + + deleteRequirement = (id) => { + let { yearId } = this.props; + let url = `/ec_years/${yearId}/ec_graduation_requirements/${id}.json`; + axios.delete(url).then(res => { + if(res){ + message.success('操作成功'); + this.getData(); + } + }).catch(e => console.log(e)) + } + + showEditContent = (index) => { + let { editIndex, graduationRequirements } = this.state; + if(editIndex !== null){ + message.error('请先保存其它内容'); + return + } + + this.setState({ editIndex: index, currentEditReq: _.cloneDeep(graduationRequirements[index])}) + } + + onEditContentChange = (e) => { + let { currentEditReq } = this.state; + currentEditReq.content = e.target.value; + this.setState({ currentEditReq }); + } + + onEditItemContentChange = (e, index) => { + let { currentEditReq } = this.state; + currentEditReq.ec_graduation_subitems[index].content = e.target.value; + this.setState({ currentEditReq }); + } + + addEditItem = () => { + let { currentEditReq } = this.state; + currentEditReq.ec_graduation_subitems.push({id: null, content: ''}) + this.setState({ currentEditReq }); + } + + removeEditItem = (index) => { + let { currentEditReq } = this.state; + currentEditReq.ec_graduation_subitems.splice(index, 1); + this.setState({ currentEditReq }); + } + + saveContentEdit = () => { + let { currentEditReq } = this.state; + + let contentExist = currentEditReq.content && currentEditReq.content.length !== 0; + let errorItem = currentEditReq.ec_graduation_subitems.find(item => !item.content || item.content.length === 0); + this.setState({ validateState: !!errorItem || !contentExist }); + + if(errorItem || !contentExist){ return } + + this.setState({ submitState: true }, this.updateRequirement); + } + + cancelContentEdit = () => { + this.setState({ currentEditReq: {}, editIndex: null, validateState: false }); + } + + updateRequirement = () => { + let { yearId } = this.props; + let { currentEditReq } = this.state; + + let url = `/ec_years/${yearId}/ec_graduation_requirements/${currentEditReq.id}.json`; + + axios.put(url, { content: currentEditReq.content, position: currentEditReq.position, graduation_subitems: currentEditReq.ec_graduation_subitems }).then(res => { + if(res){ + message.success('操作成功'); + this.setState({ submitState: false, editIndex: null }); + this.getData(); + } + }).catch(e => { + console.log(e); + this.setState({ submitState: false }); + }) + } + + showNewReqContent = () => { + let { editIndex, graduationRequirements } = this.state; + if(editIndex !== null){ + message.error('请先保存其它内容'); + return + } + + this.setState({ + editIndex: -1, addState: true, + newRequirement: { + content: '', position: graduationRequirements.length + 1, + graduation_subitems: [ + { id: null, content: '' }, + { id: null, content: '' }, + { id: null, content: '' }, + ] + } + }) + } + + onNewReqContentChange = (e) => { + let { newRequirement } = this.state; + newRequirement.content = e.target.value; + this.setState({ newRequirement }); + } + + onNewReqItemContentChange = (e, index) => { + let { newRequirement } = this.state; + newRequirement.graduation_subitems[index].content = e.target.value; + this.setState({ newRequirement }); + } + + addNewReqItem = () => { + let { newRequirement } = this.state; + newRequirement.graduation_subitems.push({id: null, content: ''}) + this.setState({ newRequirement }); + } + + removeNewReqItem = (index) => { + let { newRequirement } = this.state; + newRequirement.graduation_subitems.splice(index, 1); + this.setState({ newRequirement }); + } + + saveNewReq = () => { + let { newRequirement } = this.state; + + let contentExist = newRequirement.content && newRequirement.content.length !== 0; + let errorItem = newRequirement.graduation_subitems.find(item => !item.content || item.content.length === 0); + this.setState({ validateState: !!errorItem || !contentExist }); + + if(errorItem || !contentExist){ return } + + this.setState({ submitState: true }, this.createRequirement); + } + + cancelNewReq = () => { + this.setState({ newRequirement: {}, addState: false, editIndex: null, validateState: false }); + } + + createRequirement = () => { + let { yearId } = this.props; + let { newRequirement } = this.state; + + let url = `/ec_years/${yearId}/ec_graduation_requirements.json`; + + axios.post(url, newRequirement).then(res => { + if(res){ + message.success('操作成功'); + this.setState({ submitState: false, editIndex: null, addState: false }); + this.getData(); + } + }).catch(e => { + console.log(e); + this.setState({ submitState: false }); + }) + } + render() { let { can_manager } = this.props.year; - let { loading } = this.state; + let { loading, editIndex, addState, submitState, validateState, currentEditReq, graduationRequirements, newRequirement } = this.state; return (
@@ -56,6 +239,135 @@ class GraduationRequirement extends React.Component {
+
+
+
指标点
+
内容
+
+ { + can_manager && !addState && ( + + + + ) + } +
+
+
+ { + graduationRequirements && graduationRequirements.map((item, index) => { + return can_manager && index === editIndex ? ( +
+
+
{ index + 1 }
+
+ + + +
+
+ +
+
+ { + currentEditReq.ec_graduation_subitems.map((subitem, i) => { + return ( +
+
{ index + 1 }-{ i + 1 }
+
+ + this.onEditItemContentChange(e, i)} /> + +
+
+ this.removeEditItem(i)}/> +
+
+ ) + }) + } + +
+ + +
+
+ ) : ( +
+
+
{ index + 1 }
+
{ item.content }
+ { + can_manager && ( +
+ this.showDeleteConfirm(item.id)} /> + this.showEditContent(index)}/> + { + index === graduationRequirements.length - 1 && !addState && ( + + ) + } +
+ ) + } + +
+ { + item.ec_graduation_subitems.map((subitem, i) => { + return ( +
+
{ index + 1 }-{ i + 1 }
+
{ subitem.content }
+
+ ) + }) + } +
+ ) + }) + } + + { + can_manager && addState && ( +
+
+
{ graduationRequirements.length + 1 }
+
+ + + +
+
+ +
+
+ { + newRequirement.graduation_subitems.map((subitem, i) => { + return ( +
+
{ graduationRequirements.length + 1 }-{ i + 1 }
+
+ + this.onNewReqItemContentChange(e, i)} /> + +
+
+ this.removeNewReqItem(i)}/> +
+
+ ) + }) + } + +
+ + +
+
+ ) + } +
+
diff --git a/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.scss b/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.scss index e69de29bb..9d740a970 100644 --- a/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.scss +++ b/public/react/src/modules/ecs/EcSetting/GraduationRequirement/index.scss @@ -0,0 +1,94 @@ +.ec-graduation-requirement-page { + background: #fff; + + .graduation-requirement { + &-body { + margin-top: -24px; + } + + &-items { + &-head { + padding: 15px 30px; + display: flex; + background: #F5F5F5; + } + + &-body { + margin: 0 30px; + + &-item { + padding: 10px 0px; + border-bottom: 1px solid #eaeaea; + + &.active { + .item-row { + margin-bottom: 10px; + align-items: center; + } + + .item-column-operation { + width: 40px; + } + } + + &:last-child { + border-bottom: unset; + } + + .item-head { + margin-bottom: 10px; + font-weight: bold; + } + + .item-row { + display: flex; + } + } + } + + + .no-column { + width: 60px; + 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: 0 5px; + font-size: 16px; + cursor: pointer; + } + } + } + } + + .edit-form { + margin-top: 10px; + text-align: right; + + button { + margin-left: 10px; + } + } + + i.edit-action { + color: #29BD8B; + cursor: pointer; + font-size: 16px; + } +} \ No newline at end of file diff --git a/public/react/src/modules/ecs/EcSetting/TrainingObjective/index.js b/public/react/src/modules/ecs/EcSetting/TrainingObjective/index.js index 859b987ee..e48cf2c84 100644 --- a/public/react/src/modules/ecs/EcSetting/TrainingObjective/index.js +++ b/public/react/src/modules/ecs/EcSetting/TrainingObjective/index.js @@ -198,7 +198,7 @@ class TrainingObjective extends React.Component {
目标分解详情
{ - itemsEditState || ( + !can_manager || itemsEditState || ( diff --git a/public/react/src/modules/ecs/EcSetting/index.js b/public/react/src/modules/ecs/EcSetting/index.js index 238d8474c..4f27acb2a 100644 --- a/public/react/src/modules/ecs/EcSetting/index.js +++ b/public/react/src/modules/ecs/EcSetting/index.js @@ -10,6 +10,7 @@ import CustomLoadable from "../../../CustomLoadable"; const { Step } = Steps; const TrainingObjective = CustomLoadable(() => import('./TrainingObjective/index')) +const GraduationRequirement = CustomLoadable(() => import('./GraduationRequirement/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"]; @@ -98,6 +99,8 @@ class EcSetting extends React.Component { () }> + () }> ) }