Merge branch 'dev_item_bank' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_item_bank

dev_jupyter
杨树林 5 years ago
commit fa82b3802e

@ -0,0 +1,22 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-item-authentications-index-page').length > 0) {
var $searchFrom = $('.item-authentication-list-form');
$searchFrom.find('select[name="status"]').val('pending');
$searchFrom.on('click', '.search-form-tab', function(){
var $link = $(this);
$searchFrom.find('input[name="keyword"]').val('');
$searchFrom.find('select[name="status"]').val('processed');
if($link.data('value') === 'processed'){
$('.batch-action-container').hide();
$searchFrom.find('.status-filter').show();
} else {
$('.batch-action-container').show();
$searchFrom.find('.status-filter').hide();
$searchFrom.find('select[name="status"]').val('pending');
}
});
}
});

@ -0,0 +1,49 @@
class Admins::ItemAuthenticationsController < Admins::BaseController
def index
params[:status] ||= 'pending'
params[:sort_direction] = params[:status] == 'pending' ? 'asc' : 'desc'
applies = Admins::ApplyItemBankQuery.call(params.merge(type: "ItemBank"))
@applies = paginate applies.preload(user: { user_extension: [:school, :department] })
end
def show
apply = ApplyAction.find params[:id]
@item = ItemBank.find apply.container_id
end
def agree
Admins::ProfessionalAuths::AgreeApplyService.call(current_apply)
render_success_js
end
def refuse
Admins::ProfessionalAuths::RefuseApplyService.call(current_apply, params)
render_success_js
end
def batch_agree
ApplyUserAuthentication.professional_auth.where(id: params[:ids]).each do |apply|
begin
Admins::ProfessionalAuths::AgreeApplyService.call(apply)
rescue => e
Util.logger_error(e)
end
end
render_ok
end
def revoke
Admins::ProfessionalAuths::RevokeApplyService.call(current_apply)
render_success_js
end
private
def current_apply
@_current_apply ||= ApplyUserAuthentication.professional_auth.find(params[:id])
end
end

@ -45,6 +45,11 @@ module GitHelper
content: content, author_name: username, author_email: mail)
end
# 添加目录
def add_git_folder(folder_path, author_name, author_email, message)
GitService.add_tree(file_path: folder_path, message: message, author_name: author_name, author_email: author_email)
end
# 版本库Fork功能
def project_fork(container, original_rep_path, username)
raise Educoder::TipException.new("fork源路径为空,fork失败!") if original_rep_path.blank?

@ -56,8 +56,8 @@ class ExaminationBanksController < ApplicationController
def set_public
tip_exception(-1, "该试卷已公开") if @exam.public?
tip_exception(-1, "请勿重复提交申请") if ApplyAction.where(container: @exam).exists?
ApplyAction.create!(container: @exam, user_id: current_user.id)
tip_exception(-1, "请勿重复提交申请") if ApplyAction.where(container_id: @exam.id, container_type: "ExaminationBank").exists?
ApplyAction.create!(container_id: @exam.id, container_type: "ExaminationBank", user_id: current_user.id)
# @exam.update_attributes!(public: 1)
render_ok
end

@ -37,8 +37,8 @@ class ItemBanksController < ApplicationController
def set_public
tip_exception(-1, "该试题已公开") if @item.public?
tip_exception(-1, "请勿重复提交申请") if ApplyAction.where(container: @item).exists?
ApplyAction.create!(container: @item, user_id: current_user.id)
tip_exception(-1, "请勿重复提交申请") if ApplyAction.where(container_id: @item.id, container_type: 'ItemBank').exists?
ApplyAction.create!(container_id: @item.id, container_type: 'ItemBank', user_id: current_user.id)
# @item.update_attributes!(public: 1)
render_ok
end

@ -25,6 +25,7 @@ class ShixunsController < ApplicationController
before_action :special_allowed, only: [:send_to_course, :search_user_courses]
before_action :shixun_marker, only: [:new, :create]
skip_before_action :check_sign, only: [:download_file]
## 获取课程列表
def index
@ -892,11 +893,15 @@ class ShixunsController < ApplicationController
content = upload_file.tempfile.read
author_name = current_user.real_name
author_email = current_user.git_mail
update_file_content(content, @repo_path, author_email, author_name, "upload file by browser")
update_file_content(content, @repo_path, @path, author_email, author_name, "upload file by browser")
render_ok
end
# 上传目录
def upload_git_folder
author_name = current_user.real_name
author_email = current_user.git_mail
add_git_folder(@path, author_name, author_email, "upload folder by browser")
render_ok
end

@ -2,6 +2,7 @@ class UsersController < ApplicationController
before_action :load_user, only: [:show, :homepage_info]
before_action :check_user_exist, only: [:show, :homepage_info]
skip_before_action :check_sign, only: [:attachment_show]
# 检查是否更新
def system_update

@ -16,7 +16,7 @@ class ApplyAction < ApplicationRecord
tidings.create(user_id: user_id, trigger_user_id: 0, status: 1, viewed: 0, tiding_type: 'System',
parent_container_id: container_id, parent_container_type: container_type,
belong_container_id: container_id, belong_container_type: 'User')
else
elsif %w(ApplyShixun ApplySubject TrialAuthorization).include?(container_type)
belong_container_type = if container_type == 'TrialAuthorization'
'User'
else

@ -17,4 +17,24 @@ class ItemBank < ApplicationRecord
def analysis
item_analysis&.analysis
end
def type_string
result = case item_type
when "SINGLE"
"单选题"
when "MULTIPLE"
"多选题"
when "JUDGMENT"
"判断题"
when "COMPLETION"
"填空题"
when "SUBJECTIVE"
"简答题"
when "PRACTICAL"
"实训题"
when "PROGRAM"
"编程题"
end
result
end
end

@ -0,0 +1,34 @@
class Admins::ApplyItemBankQuery < ApplicationQuery
include CustomSortable
attr_reader :params
sort_columns :updated_at, default_by: :updated_at, default_direction: :desc
def initialize(params)
@params = params
end
def call
applies = ApplyAction.where(container_type: params[:type].presence || "ItemBank")
status =
case params[:status]
when 'pending' then 0
when 'processed' then [1, 2]
when 'agreed' then 1
when 'refused' then 2
else 0
end
applies = applies.where(status: status) if status.present?
# 关键字模糊查询
keyword = params[:keyword].to_s.strip
if keyword.present?
applies = applies.joins(user: { user_extension: :school })
.where('CONCAT(lastname,firstname) LIKE :keyword OR schools.name LIKE :keyword', keyword: "%#{keyword}%")
end
custom_sort(applies, params[:sort_by], params[:sort_direction])
end
end

@ -6,7 +6,7 @@ class GitService
class << self
['add_repository', 'fork_repository', 'delete_repository', 'file_tree', 'update_file', 'file_content', 'commits'].each do |method|
['add_repository', 'fork_repository', 'delete_repository', 'file_tree', 'update_file', 'file_content', 'commits', 'add_tree'].each do |method|
define_method method do |params|
post(method, params)
end

@ -0,0 +1,30 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('题库审批') %>
<% end %>
<div class="box search-form-container flex-column mb-0 pb-0 item-authentication-list-form">
<ul class="nav nav-tabs w-100 search-form-tabs">
<li class="nav-item">
<%= link_to '待审批', admins_item_authentications_path(status: :pending), remote: true, 'data-value': 'pending',
class: "nav-link search-form-tab #{params[:status] == 'pending' ? 'active' : ''}" %>
</li>
<li class="nav-item">
<%= link_to '已审批', admins_item_authentications_path(status: :processed), remote: true, 'data-value': 'processed',
class: "nav-link search-form-tab #{params[:status] != 'pending' ? 'active' : ''}" %>
</li>
</ul>
<%= form_tag(admins_item_authentications_path(unsafe_params), method: :get, class: 'form-inline search-form justify-content-end mt-3', remote: true) do %>
<div class="form-group status-filter" style="<%= params[:status] != 'pending' ? '' : 'display: none;' %>">
<label for="status">审核状态:</label>
<% status_options = [['全部', 'processed'], ['已同意', 'agreed'], ['已拒绝', 'refused']] %>
<%= select_tag(:status, options_for_select(status_options), class: 'form-control') %>
</div>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '姓名/学校/单位检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<% end %>
</div>
<div class="box admin-list-container item-authentication-list-container">
<%= render(partial: 'admins/item_authentications/shared/list', locals: { applies: @applies }) %>
</div>

@ -0,0 +1 @@
$('.item-authentication-list-container').html("<%= j( render partial: 'admins/item_authentications/shared/list', locals: { applies: @applies } ) %>");

@ -0,0 +1,58 @@
<% is_processed = params[:status].to_s != 'pending' %>
<table class="table table-hover text-center professional-authentication-list-table">
<thead class="thead-light">
<tr>
<th width="4%">序号</th>
<th width="8%">头像</th>
<th width="14%">创建者</th>
<th width="10%">学校</th>
<th width="24%">试题</th>
<th width="8%">题型</th>
<th width="16%">提交时间</th>
<% if !is_processed %>
<th width="16%">操作</th>
<% end %>
</tr>
</thead>
<tbody>
<% if applies.present? %>
<% applies.each_with_index do |apply, index| %>
<% user = apply.user %>
<% item = ItemBank.find apply.container_id %>
<tr class="item-authentication-item item-authentication-<%= apply.id %>">
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td>
<%= link_to "/users/#{user.login}", class: 'item-authentication-avatar', target: '_blank', data: { toggle: 'tooltip', title: '个人主页' } do %>
<img src="/images/<%= url_to_avatar(user) %>" class="rounded-circle" width="40" height="40" />
<% end %>
</td>
<td><%= user.real_name %></td>
<td><%= raw [user.school_name.presence, user.department_name.presence].compact.join('<br/>') %></td>
<td>
<% if item.item_type == "PROGRAM" %>
<%= link_to item.name, "/questions/#{item.identifier}", target: "_blank" %>
<% else %>
<%= link_to item.name, admins_item_authentication_path(apply), remote: true %>
<% end %>
</td>
<td><%= item.type_string %></td>
<td><%= apply.updated_at.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">
<%= agree_link '同意', agree_admins_item_authentication_path(apply, element: ".item-authentication-#{apply.id}"), 'data-confirm': '确认审核通过?', 'data-disable-with': "提交中..." %>
<%= javascript_void_link('拒绝', class: 'action refuse-action',
data: {
toggle: 'modal', target: '.admin-common-refuse-modal', id: apply.id,
url: refuse_admins_item_authentication_path(apply, element: ".item-authentication-#{apply.id}")
}, 'data-disable-with': "拒绝中...") %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: applies } %>

@ -75,7 +75,8 @@
<li><%= sidebar_item(admins_library_applies_path, '教学案例发布', icon: 'language', controller: 'admins-library_applies') %></li>
<li><%= sidebar_item(admins_project_package_applies_path, '众包需求发布', icon: 'joomla', controller: 'admins-project_package_applies') %></li>
<li><%= sidebar_item(admins_video_applies_path, '视频发布', icon: 'film', controller: 'admins-video_applies') %></li>
<% end %>
<li><%= sidebar_item(admins_item_authentications_path, '试题发布', icon: 'question', controller: 'admins-item_authentications') %></li>
<% end %>
</li>
<li>

@ -1152,6 +1152,12 @@ Rails.application.routes.draw do
post :refuse
end
end
resources :item_authentications, only: [:index, :show] do
member do
post :agree
post :refuse
end
end
resources :shixuns, only: [:index,:destroy]
resources :shixun_settings, only: [:index,:update]
resources :shixun_feedback_messages, only: [:index]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-03 09:20:12
* @LastEditTime : 2020-01-03 11:28:26
*/
import './index.scss';
// import 'katex/dist/katex.css';
@ -324,6 +324,7 @@ class EditTab extends React.Component {
console.log(choid_ids);
return (
<Cascader
placeholder="请选择"
options={tempArr}
expandTrigger="hover"
value={choid_ids}

@ -87,15 +87,16 @@
right: 150px;
top: 0;
height: 34px;
background: gold;
line-height: 32px;
/* background: gold; */
}
.code_evalute_icon{
position: absolute;
top: 0;
width: 56px;
height: 28px;
width: 54px;
height: 27px;
left: 50%;
margin-left: -28px;
margin-left: -27px;
background: rgba(42,58,79,1);
z-index: 10;
border-bottom-left-radius: 100px;
@ -106,6 +107,15 @@
opacity: .4;
transition: all .3s;
}
.code_evalute_icon:hover{
opacity: 1;
}
.btn-arrow{
position: relative;
bottom: 3px;
font-size: 14px !important;
}
@keyframes mymove
{

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import axios from 'axios'
import { Spin, Icon } from 'antd'
import ClipboardJS from 'clipboard'
import { connect } from 'react-redux';
import VNCDisplay from './VNCDisplay'
import FloatButton from './component/FloatButton'
import SecondDrawer from './component/SecondDrawer'
@ -202,13 +202,17 @@ class VNCContainer extends Component {
onSelect={onTreeSelect}
*/
render() {
const { challenge, vnc_url, git_url } = this.props
const { challenge, vnc_url, git_url, showOrHide, isCollapse } = this.props
const _classCtx = this.state.bottomDrawer ? 'btn_test_case_active' : 'btn_test_case';
const _classes = this.state.bottomDrawer
? `iconfont icon-xiajiantou btn-arrow`
: 'iconfont icon-shangjiantou btn-arrow';
const secondDrawerChildren = this.renderSecondDrawerChildren();
const _drawClasses = showOrHide
? ('codeEvaluateDrawer code_evaluate_stretch')
: (isCollapse ? 'codeEvaluateDrawer active' : 'codeEvaluateDrawer');
return (
<React.Fragment>
<SecondDrawer
@ -225,9 +229,20 @@ class VNCContainer extends Component {
<style>{`
/* 评测结果 */
.codeEvaluateDrawer{
// position: absolute;
// bottom: 84px;
position: absolute;
bottom: 84px;
transition: all .3s;
// bottom: px;
}
.codeEvaluateDrawer.active{
bottom: 50px;
}
.code_evaluate_stretch{
top: 0px;
}
.codeEvaluateDrawer #game_test_set_results {
height: 198px;
}
@ -242,6 +257,14 @@ class VNCContainer extends Component {
background: rgb(5, 16, 26) !important;
}
.code_evaluate_stretch .ant-drawer-content-wrapper{
height: 100% !important;
}
.code_evaluate_stretch #game_test_set_results {
height: calc(100vh - 136px) !important;
}
.codeEvaluateFloatButton {
bottom: 180px !important;
left: unset;
@ -348,12 +371,12 @@ class VNCContainer extends Component {
width={firstDrawerWidth}
closable={false}
onClose={this.onBottomDrawerClose}
visible={this.state.bottomDrawer===undefined?false:this.state.bottomDrawer}
className={'codeEvaluateDrawer'}
visible={isCollapse}
className={_drawClasses}
placement="bottom"
getContainer={false}
// style={{ position: 'absolute', bottom: '-25px', zIndex: 1 }}
style={{ position: 'absolute', bottom: '50px', zIndex: 1 }}
// style={{ position: 'absolute', bottom: '50px', top: 0, zIndex: 1 }}
afterVisibleChange={(visible) => {
if (visible) {
const canvas = $('.vncDisply canvas')[0]
@ -364,9 +387,9 @@ class VNCContainer extends Component {
>
{ this.props.codeEvaluate }
</Drawer>
<FloatButton onClick={this.swtichBottomDrawer}
{/* <FloatButton onClick={this.swtichBottomDrawer}
className="codeEvaluateFloatButton"
>测试集</FloatButton>
>测试集</FloatButton> */}
{/* <div
className={_classCtx}
onClick={this.swtichBottomDrawer}
@ -382,4 +405,14 @@ class VNCContainer extends Component {
}
}
export default VNCContainer;
const mapStateToProps = (state) => {
const { showOrHide, isCollapse } = state.tpiReducer;
return {
showOrHide,
isCollapse
}
};
export default connect(
mapStateToProps
)(VNCContainer);

@ -4,10 +4,11 @@ import React, { Component } from 'react';
import IconButton from 'material-ui/IconButton';
import Tooltip from 'material-ui/Tooltip';
import Button from 'material-ui/Button';
import { connect } from 'react-redux';
import './CodeEvaluateView.css'
import { CircularProgress } from 'material-ui/Progress';
import { on, off } from 'educoder'
import actions from '../../../redux/actions';
const testSetsExpandedArrayInitVal = [false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false,
@ -274,9 +275,24 @@ class CodeEvaluateView extends Component {
})
}
onHandleTestCase () {
// console.log(this);
const {showOrHide, showOrHideTpiTestCase} = this.props;
showOrHideTpiTestCase(!showOrHide);
}
handleShowTestCase () {
// console.log('111111111');
const {isCollapse, isCollpaseTsetCase, showOrHideTpiTestCase} = this.props;
isCollpaseTsetCase(!isCollapse);
if (isCollapse) {
showOrHideTpiTestCase(false);
}
}
render() {
const { evaluateViewExpanded, tabIndex } = this.state;
const { output_sets, latest_output, record, challenge, gameBuilding, myshixun } = this.props;
const { output_sets, latest_output, record, challenge, gameBuilding, myshixun, showOrHide, isCollapse } = this.props;
if (!output_sets) {
return (
@ -300,6 +316,8 @@ class CodeEvaluateView extends Component {
onclick="check_tab('blacktab_con','blacktab_hover',this);"
*/
const _arrowClasses = isCollapse ? 'iconfont icon-xiajiantou btn-arrow' : 'iconfont icon-shangjiantou btn-arrow';
return (
<React.Fragment>
<ul id="blacktab_nav">
@ -312,17 +330,20 @@ class CodeEvaluateView extends Component {
<li className={`blacktab_con ${ tabIndex === 1 ? 'tab_hover' : ''}`} onClick={() => this.tabIndexChange(1)}>
<a href="javascript:void(0);" className="tab_type tab_color">测试结果</a>
</li>
{/* <li className="blacktab_con_abs">
<span className="code_evalute_icon">
<span className="iconfont icon-xiajiantou btn-arrow"></span>
<li className="blacktab_con_abs">
<span className="code_evalute_icon" onClick={() => this.handleShowTestCase()}>
<span className={_arrowClasses}></span>
</span>
</li> */}
</li>
{this.props.inDrawer ? <Tooltip id="tooltip-icon-expand" title={ "收起" }>
{this.props.inDrawer ? <Tooltip id="tooltip-icon-expand" title={ showOrHide ? "收起" : "展开"}>
{/*TODO 按钮大小改造css*/}
{/* icon-guanbi */}
<a className="iconButton fr mr15 mt4" onClick={this.props.hideCodeEvaluate} id="extend_and_zoom" >
{/* <a className="iconButton fr mr15 mt4" onClick={this.props.hideCodeEvaluate} id="extend_and_zoom" >
<i className={ "font-18 iconfont icon-guanbi" }></i>
</a> */}
<a style={{ display: isCollapse ? 'inline-block' : 'none'}} className="iconButton fr mr15" id="extend_and_zoom" onClick={() => this.onHandleTestCase()}>
<i className={ showOrHide ? "font-18 iconfont icon-shousuo" : "iconfont icon-zhankai font-18" }></i>
</a>
</Tooltip> : <Tooltip id="tooltip-icon-expand" title={ evaluateViewExpanded ? "" : ""}>
@ -398,4 +419,19 @@ class CodeEvaluateView extends Component {
}
}
export default CodeEvaluateView;
const mapStateToProps = (state) => {
const { showOrHide, isCollapse } = state.tpiReducer;
return {
showOrHide,
isCollapse
}
};
const mapDispatchToProps = (dispatch) => ({
showOrHideTpiTestCase: (flag) => dispatch(actions.showOrHideTpiTestCase(flag)),
isCollpaseTsetCase: (flag) => dispatch(actions.isCollpaseTsetCase(flag))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(CodeEvaluateView);

@ -84,6 +84,9 @@ const types = {
DELETE_COMMENTS: 'DELETE_COMMENTS', // 删除评论
SAVE_COMMENT_IDENTIFIER: 'SAVE_COMMENT_IDENTIFIER', // 评论时的identifier
CHANGE_COMMENT_PAGINATION_PARAMS: 'CHANGE_COMMENT_PAGINATION_PARAMS', // 改变分页
/** tpi */
SHOW_OR_HIDE_TPI_TEST_CASE: 'SHOW_OR_HIDE_TPI_TEST_CASE', // 显示或隐藏tpi测试集弹框
IS_COLLAPSE_TEST_CASE: 'IS_COLLAPSE_TEST_CASE' // 是否展开测试集
}
export default types;

@ -96,6 +96,11 @@ import {
updataspinning
} from './jupyter';
import {
showOrHideTpiTestCase,
isCollpaseTsetCase
} from './tpi';
export default {
toggleTodo,
getOJList,
@ -169,5 +174,8 @@ export default {
deleteComment,
likeComment,
showOrHideComment,
changePagination
changePagination,
// tpi
showOrHideTpiTestCase,
isCollpaseTsetCase
}

@ -0,0 +1,23 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-03 10:24:43
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-03 11:45:22
*/
import types from './actionTypes';
export const showOrHideTpiTestCase = (flag) => {
return {
type: types.SHOW_OR_HIDE_TPI_TEST_CASE,
payload: flag
}
}
export const isCollpaseTsetCase = (flag) => {
return {
type: types.IS_COLLAPSE_TEST_CASE,
payload: flag
}
}

@ -15,6 +15,7 @@ import commonReducer from './commonReducer';
import userReducer from './userReducer';
import jupyterReducer from './jupyterReducer';
import commentReducer from './commentReducer';
import tpiReducer from './tpiReducer';
export default combineReducers({
testReducer,
@ -24,5 +25,6 @@ export default combineReducers({
commonReducer,
userReducer,
jupyterReducer,
commentReducer
commentReducer,
tpiReducer
});

@ -0,0 +1,36 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-03 10:24:31
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-03 11:44:26
*/
import types from "../actions/actionTypes";
const initialState = {
showOrHide: false,
isCollapse: false, // 是否展开测试集
};
const tpiReducer = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case types.SHOW_OR_HIDE_TPI_TEST_CASE:
return {
...state,
showOrHide: payload
}
case types.IS_COLLAPSE_TEST_CASE:
return {
...state,
isCollapse: payload
}
default:
return {
...state
}
}
}
export default tpiReducer;
Loading…
Cancel
Save