commit
93648dffeb
@ -0,0 +1,91 @@
|
||||
//= require rails-ujs
|
||||
//= require activestorage
|
||||
//= require turbolinks
|
||||
//= require jquery3
|
||||
//= require popper
|
||||
//= require bootstrap-sprockets
|
||||
//= require jquery.validate.min
|
||||
//= require additional-methods.min
|
||||
//= require bootstrap-notify
|
||||
//= require jquery.cookie.min
|
||||
//= require select2
|
||||
//= require jquery.cxselect
|
||||
//= require bootstrap-datepicker
|
||||
//= require bootstrap.viewer
|
||||
//= require jquery.mloading
|
||||
//= require jquery-confirm.min
|
||||
//= require common
|
||||
|
||||
//= require echarts
|
||||
//= require codemirror/lib/codemirror
|
||||
//= require codemirror/mode/shell/shell
|
||||
//= require editormd/editormd
|
||||
//= require editormd/languages/zh-tw
|
||||
//= require dragula/dragula
|
||||
|
||||
//= require_tree ./i18n
|
||||
//= require_tree ./cooperative
|
||||
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
|
||||
}
|
||||
});
|
||||
|
||||
// ******** select2 global config ********
|
||||
$.fn.select2.defaults.set('theme', 'bootstrap4');
|
||||
$.fn.select2.defaults.set('language', 'zh-CN');
|
||||
|
||||
Turbolinks.setProgressBarDelay(200);
|
||||
|
||||
$.notifyDefaults({
|
||||
type: 'success',
|
||||
z_index: 9999,
|
||||
delay: 2000
|
||||
});
|
||||
|
||||
$(document).on('turbolinks:load', function(){
|
||||
$('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' });
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
// 图片查看大图
|
||||
$('img.preview-image').bootstrapViewer();
|
||||
|
||||
// flash alert提示框自动关闭
|
||||
if($('.cooperative-alert-container .alert').length > 0){
|
||||
setTimeout(function(){
|
||||
$('.cooperative-alert-container .alert:not(.alert-danger)').alert('close');
|
||||
}, 2000);
|
||||
setTimeout(function(){
|
||||
$('.cooperative-alert-container .alert.alert-danger').alert('close');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("turbolinks:before-cache", function () {
|
||||
$('[data-toggle="tooltip"]').tooltip('hide');
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
});
|
||||
// var progressBar = new Turbolinks.ProgressBar();
|
||||
|
||||
// $(document).on('ajax:send', function(event){
|
||||
// console.log('ajax send', event);
|
||||
// progressBar.setValue(0)
|
||||
// progressBar.show()
|
||||
// });
|
||||
//
|
||||
// $(document).on('ajax:complete', function(event){
|
||||
// console.log('ajax complete', event);
|
||||
// progressBar.setValue(1)
|
||||
// progressBar.hide() // 分页时不触发,奇怪
|
||||
// });
|
||||
// $(document).on('ajax:success', function(event){
|
||||
// console.log('ajax success', event);
|
||||
// });
|
||||
// $(document).on('ajax:error', function(event){
|
||||
// console.log('ajax error', event);
|
||||
// });
|
||||
|
||||
$(function () {
|
||||
});
|
@ -0,0 +1,125 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.cooperative-carousels-index-page').length > 0) {
|
||||
// ------------ 保存链接 -----------
|
||||
$('.carousels-card').on('click', '.save-data-btn', function(){
|
||||
var $link = $(this);
|
||||
var id = $link.data('id');
|
||||
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
|
||||
var name = $('.custom-carousel-item-' + id).find('.name-input').val();
|
||||
if(!name || name.length == 0){
|
||||
$.notify({ message: '名称不能为空' },{ type: 'danger' });
|
||||
return;
|
||||
}
|
||||
$link.attr('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/cooperative/carousels/' + id,
|
||||
method: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { link: link, name: name },
|
||||
success: function(data){
|
||||
$.notify({ message: '操作成功' });
|
||||
},
|
||||
error: ajaxErrorNotifyHandler,
|
||||
complete: function(){
|
||||
$link.removeAttr('disabled');
|
||||
}
|
||||
})
|
||||
});
|
||||
// -------------- 是否在首页展示 --------------
|
||||
$('.carousels-card').on('change', '.online-check-box', function(){
|
||||
var $checkbox = $(this);
|
||||
var id = $checkbox.data('id');
|
||||
var checked = $checkbox.is(':checked');
|
||||
$checkbox.attr('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/cooperative/carousels/' + id,
|
||||
method: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { status: checked },
|
||||
success: function(data){
|
||||
$.notify({ message: '保存成功' });
|
||||
var box = $('.custom-carousel-item-' + id).find('.drag');
|
||||
if(checked){
|
||||
box.removeClass('not_active');
|
||||
}else{
|
||||
box.addClass('not_active');
|
||||
}
|
||||
},
|
||||
error: ajaxErrorNotifyHandler,
|
||||
complete: function(){
|
||||
$checkbox.removeAttr('disabled');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// ------------ 拖拽 -------------
|
||||
var onDropFunc = function(el, _target, _source, sibling){
|
||||
var moveId = $(el).data('id');
|
||||
var insertId = $(sibling).data('id') || '';
|
||||
|
||||
$.ajax({
|
||||
url: '/cooperative/carousels/drag',
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
data: { move_id: moveId, after_id: insertId },
|
||||
success: function(data){
|
||||
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
|
||||
$(ele).html(index + 1);
|
||||
})
|
||||
},
|
||||
error: function(res){
|
||||
var data = res.responseJSON;
|
||||
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
|
||||
}
|
||||
})
|
||||
};
|
||||
var ele1 = document.getElementById('carousels-container');
|
||||
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
|
||||
|
||||
|
||||
// ----------- 新增 --------------
|
||||
var $createModal = $('.modal.cooperative-add-carousel-modal');
|
||||
var $createForm = $createModal.find('form.cooperative-add-carousel-form');
|
||||
|
||||
$createForm.validate({
|
||||
errorElement: 'span',
|
||||
errorClass: 'danger text-danger',
|
||||
rules: {
|
||||
"portal_image[image]": {
|
||||
required: true
|
||||
},
|
||||
"portal_image[name]": {
|
||||
required: true
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
$createModal.on('show.bs.modal', function(event){
|
||||
resetFileInputFunc($createModal.find('.img-file-input'));
|
||||
$createModal.find('.file-names').html('选择文件');
|
||||
});
|
||||
|
||||
$createModal.on('click', '.submit-btn', function() {
|
||||
$createForm.find('.error').html('');
|
||||
|
||||
if ($createForm.valid()) {
|
||||
$createForm.submit();
|
||||
} else {
|
||||
$createForm.find('.error').html('请选择图片');
|
||||
}
|
||||
});
|
||||
$createModal.on('change', '.img-file-input', function(){
|
||||
var file = $(this)[0].files[0];
|
||||
$createModal.find('.file-names').html(file ? file.name : '请选择文件');
|
||||
})
|
||||
|
||||
// -------------- 重新上传图片 --------------
|
||||
//replace_image_url
|
||||
$('.modal.cooperative-upload-file-modal').on('upload:success', function(e, data){
|
||||
var $carouselItem = $('.custom-carousel-item-' + data.source_id);
|
||||
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
|
||||
})
|
||||
}
|
||||
})
|
@ -0,0 +1,86 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.cooperative-laboratory-settings-edit-page, body.cooperative-laboratory-settings-update-page').length > 0) {
|
||||
var $container = $('.edit-laboratory-setting-container');
|
||||
var $form = $container.find('.edit_laboratory');
|
||||
|
||||
$('.logo-item-left').on("change", 'input[type="file"]', function () {
|
||||
var $fileInput = $(this);
|
||||
var file = this.files[0];
|
||||
var imageType = /image.*/;
|
||||
if (file && file.type.match(imageType)) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
var $box = $fileInput.parent();
|
||||
$box.find('img').attr('src', reader.result).css('display', 'block');
|
||||
$box.addClass('has-img');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
}
|
||||
});
|
||||
|
||||
createMDEditor('laboratory-footer-editor', { height: 200, placeholder: '请输入备案信息' });
|
||||
|
||||
$form.validate({
|
||||
errorElement: 'span',
|
||||
errorClass: 'danger text-danger',
|
||||
errorPlacement:function(error,element){
|
||||
if(element.parent().hasClass("input-group")){
|
||||
element.parent().after(error);
|
||||
}else{
|
||||
element.after(error)
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
identifier: {
|
||||
required: true,
|
||||
checkSite: true
|
||||
},
|
||||
name: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
$.validator.addMethod("checkSite",function(value,element,params){
|
||||
var checkSite = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
|
||||
return this.optional(element)||(checkSite.test(value + '.educoder.com'));
|
||||
},"域名不合法!");
|
||||
|
||||
$form.on('click', '.submit-btn', function(){
|
||||
$form.find('.submit-btn').attr('disabled', 'disabled');
|
||||
$form.find('.error').html('');
|
||||
var valid = $form.valid();
|
||||
|
||||
$('input[name="navbar[][name]"]').each(function(_, e){
|
||||
var $ele = $(e);
|
||||
if($ele.val() === undefined || $ele.val().length === 0){
|
||||
$ele.addClass('danger text-danger');
|
||||
valid = false;
|
||||
} else {
|
||||
$ele.removeClass('danger text-danger');
|
||||
}
|
||||
});
|
||||
|
||||
if(!valid) return;
|
||||
$.ajax({
|
||||
method: 'PATCH',
|
||||
dataType: 'json',
|
||||
url: $form.attr('action'),
|
||||
data: new FormData($form[0]),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(data){
|
||||
$.notify({ message: '保存成功' });
|
||||
window.location.reload();
|
||||
},
|
||||
error: function(res){
|
||||
var data = res.responseJSON;
|
||||
$form.find('.error').html(data.message);
|
||||
},
|
||||
complete: function(){
|
||||
$form.find('.submit-btn').attr('disabled', false);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.cooperative-laboratory-users-index-page').length > 0) {
|
||||
// ============= 添加管理员 ==============
|
||||
var $addMemberModal = $('.cooperative-add-laboratory-user-modal');
|
||||
var $addMemberForm = $addMemberModal.find('.cooperative-add-laboratory-user-form');
|
||||
var $memberSelect = $addMemberModal.find('.laboratory-user-select');
|
||||
|
||||
$addMemberModal.on('show.bs.modal', function(event){
|
||||
$memberSelect.select2('val', ' ');
|
||||
});
|
||||
|
||||
$memberSelect.select2({
|
||||
theme: 'bootstrap4',
|
||||
placeholder: '请输入要添加的管理员姓名',
|
||||
multiple: true,
|
||||
minimumInputLength: 1,
|
||||
ajax: {
|
||||
delay: 500,
|
||||
url: '/cooperative/users',
|
||||
dataType: 'json',
|
||||
data: function(params){
|
||||
return { name: params.term };
|
||||
},
|
||||
processResults: function(data){
|
||||
return { results: data.users }
|
||||
}
|
||||
},
|
||||
templateResult: function (item) {
|
||||
if(!item.id || item.id === '') return item.text;
|
||||
return item.real_name + "--" + item.identity;
|
||||
},
|
||||
templateSelection: function(item){
|
||||
if (item.id) {
|
||||
}
|
||||
return item.real_name || item.text;
|
||||
}
|
||||
});
|
||||
|
||||
$addMemberModal.on('click', '.submit-btn', function(){
|
||||
$addMemberForm.find('.error').html('');
|
||||
|
||||
var memberIds = $memberSelect.val();
|
||||
if (memberIds && memberIds.length > 0) {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
url: '/cooperative/laboratory_users',
|
||||
data: { user_ids: memberIds },
|
||||
success: function(data){
|
||||
if(data && data.status == 0){
|
||||
show_success_flash();
|
||||
$addMemberModal.modal('hide');
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$addMemberModal.modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
var $modal = $('.modal.cooperative-upload-file-modal');
|
||||
if ($modal.length > 0) {
|
||||
var $form = $modal.find('form.cooperative-upload-file-form')
|
||||
var $sourceIdInput = $modal.find('input[name="source_id"]');
|
||||
var $sourceTypeInput = $modal.find('input[name="source_type"]');
|
||||
|
||||
$modal.on('show.bs.modal', function(event){
|
||||
var $link = $(event.relatedTarget);
|
||||
var sourceId = $link.data('sourceId');
|
||||
var sourceType = $link.data('sourceType');
|
||||
|
||||
$sourceIdInput.val(sourceId);
|
||||
$sourceTypeInput.val(sourceType);
|
||||
|
||||
$modal.find('.upload-file-input').trigger('click');
|
||||
});
|
||||
|
||||
$modal.find('.upload-file-input').on('change', function(e){
|
||||
var file = $(this)[0].files[0];
|
||||
|
||||
if(file){
|
||||
$modal.find('.file-names').html(file.name);
|
||||
$modal.find('.submit-btn').trigger('click');
|
||||
}
|
||||
})
|
||||
|
||||
var formValid = function(){
|
||||
if($form.find('input[name="file"]').val() == undefined || $form.find('input[name="file"]').val().length == 0){
|
||||
$form.find('.error').html('请选择文件');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$modal.on('click', '.submit-btn', function(){
|
||||
$form.find('.error').html('');
|
||||
|
||||
if (formValid()) {
|
||||
var formDataString = $form.serialize();
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
url: '/cooperatives/files?' + formDataString,
|
||||
data: new FormData($form[0]),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(data){
|
||||
$.notify({ message: '上传成功' });
|
||||
$modal.trigger('upload:success', data);
|
||||
$modal.modal('hide');
|
||||
},
|
||||
error: function(res){
|
||||
var data = res.responseJSON;
|
||||
$form.find('.error').html(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
$(document).on('turbolinks:load', function(){
|
||||
$('#sidebarCollapse').on('click', function () {
|
||||
$(this).toggleClass('active');
|
||||
$('#sidebar').toggleClass('active');
|
||||
$.cookie('cooperative_sidebar_collapse', $(this).hasClass('active'), {path: '/cooperative'});
|
||||
});
|
||||
|
||||
var sidebarController = $('#sidebar').data('current-controller');
|
||||
if (sidebarController.length > 0) {
|
||||
$('#sidebar a.active').removeClass('active');
|
||||
$('#sidebar ul.collapse.show').removeClass('show');
|
||||
var activeLi = $('#sidebar a[data-controller="' + sidebarController + '"]');
|
||||
activeLi.addClass('active');
|
||||
activeLi.parent().parent('ul.collapse').addClass('show');
|
||||
}
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
|
||||
* vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_tree .
|
||||
*= require_self
|
||||
|
||||
*/
|
@ -0,0 +1,54 @@
|
||||
@import "bootstrap";
|
||||
@import "font-awesome-sprockets";
|
||||
@import "font-awesome";
|
||||
@import "select2.min";
|
||||
@import "select2-bootstrap4.min";
|
||||
@import "bootstrap-datepicker";
|
||||
@import "bootstrap-datepicker.standalone";
|
||||
@import "jquery.mloading";
|
||||
@import "jquery-confirm.min";
|
||||
|
||||
@import "codemirror/lib/codemirror";
|
||||
@import "editormd/css/editormd.min";
|
||||
@import "dragula/dragula";
|
||||
|
||||
@import "common";
|
||||
@import "cooperative/*";
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
font-size: 14px;
|
||||
background: #efefef;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.simple_form {
|
||||
.form-group {
|
||||
.collection_radio_buttons {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.form-check-inline {
|
||||
height: calc(1.5em + 0.75rem + 2px)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-default{
|
||||
color: #666;
|
||||
background: #e1e1e1!important;
|
||||
}
|
||||
.export-absolute{
|
||||
right:20px;
|
||||
position: absolute;
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
.cooperative-carousels-index-page {
|
||||
.carousels-card {
|
||||
.custom-carousel-item {
|
||||
& > .drag {
|
||||
cursor: move;
|
||||
background: #fff;
|
||||
box-shadow: 1px 2px 5px 3px #f0f0f0;
|
||||
}
|
||||
|
||||
&-no {
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-img {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
|
||||
& > img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #F5F5F5;
|
||||
}
|
||||
}
|
||||
|
||||
.not_active {
|
||||
background: #F0F0F0;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
font-size: 20px;
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.save-url-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.operate-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.online-check-box {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.name-input {
|
||||
flex: 1;
|
||||
}
|
||||
.link-input {
|
||||
flex: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
.cooperative-body-container {
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
|
||||
& > .content {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
|
||||
.box {
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 面包屑 */
|
||||
.breadcrumb {
|
||||
padding-left: 5px;
|
||||
font-size: 20px;
|
||||
background: unset;
|
||||
}
|
||||
|
||||
/* 内容表格 */
|
||||
table {
|
||||
table-layout: fixed;
|
||||
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr {
|
||||
&.no-data {
|
||||
&:hover {
|
||||
color: darkgrey;
|
||||
background: unset;
|
||||
}
|
||||
|
||||
& > td {
|
||||
text-align: center;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-container {
|
||||
& > .action {
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.more-action-dropdown {
|
||||
.dropdown-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.paginate-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.paginate-total {
|
||||
margin-bottom: 10px;
|
||||
color: darkgrey;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索表单 */
|
||||
.search-form-container {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-form {
|
||||
flex: 1;
|
||||
|
||||
* { font-size: 14px; }
|
||||
|
||||
select, input {
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.global-error {
|
||||
color: grey;
|
||||
min-height: 300px;
|
||||
|
||||
&-code {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
.nav-link {
|
||||
padding: 0.5rem 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
.batch-action-container {
|
||||
margin-bottom: -15px;
|
||||
padding: 10px 20px 0;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
.cooperative-laboratory-settings-edit-page, .cooperative-laboratory-settings-update-page {
|
||||
.edit-laboratory-setting-container {
|
||||
.logo-item {
|
||||
display: flex;
|
||||
|
||||
&-img {
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
&-upload {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: #F5F5F5;
|
||||
border: 1px solid #E5E5E5;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 27px;
|
||||
left: 39px;
|
||||
width: 2px;
|
||||
height: 26px;
|
||||
background: #E5E5E5;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 39px;
|
||||
left: 27px;
|
||||
width: 26px;
|
||||
height: 2px;
|
||||
background: #E5E5E5;
|
||||
}
|
||||
}
|
||||
|
||||
&-left {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
|
||||
&.has-img {
|
||||
.logo-item-upload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.logo-item-upload {
|
||||
display: block;
|
||||
background: rgba(145, 145, 145, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: #777777;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: #23272B;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
#sidebar {
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
background: #272822;
|
||||
color: #fff;
|
||||
transition: all 0.5s;
|
||||
overflow-y: scroll;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display:none
|
||||
}
|
||||
|
||||
&.active {
|
||||
min-width: 60px;
|
||||
max-width: 60px;
|
||||
text-align: center;
|
||||
|
||||
.sidebar-header {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-logo {
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > .logo-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul li a {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-size: 0.85em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
span { display: none }
|
||||
|
||||
i {
|
||||
margin-right: 0;
|
||||
display: block;
|
||||
font-size: 1.8em;
|
||||
margin-bottom: 5px;
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-toggle::after {
|
||||
top: auto;
|
||||
bottom: 10px;
|
||||
right: 50%;
|
||||
-webkit-transform: translateX(50%);
|
||||
-ms-transform: translateX(50%);
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
ul ul a {
|
||||
padding: 10px !important;
|
||||
|
||||
span { display: none }
|
||||
|
||||
i {
|
||||
margin-left: 0px;
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
width: 30px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
background: #272822;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
&-logo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
& > img {
|
||||
width: 40px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
& > .logo-label {
|
||||
font-size: 18px;
|
||||
color: darkgrey;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#sidebarCollapse {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
|
||||
&.active {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
background: #3f3f3f;
|
||||
border: 1px solid grey;
|
||||
border-radius: 3px;
|
||||
|
||||
i.fold { display: none; }
|
||||
i.unfold { display: block; }
|
||||
}
|
||||
|
||||
i.fold {
|
||||
display: block;
|
||||
}
|
||||
i.unfold { display: none; }
|
||||
}
|
||||
|
||||
a, a:hover, a:focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
& > ul > li > a > i {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
ul {
|
||||
&.components {
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #3f3f3f;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
li > a {
|
||||
padding: 10px;
|
||||
font-size: 1em;
|
||||
display: block;
|
||||
text-align: left;
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
font-size: 1em;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
li a {
|
||||
&:hover, &.active {
|
||||
color: #fff;
|
||||
background: #276891;
|
||||
}
|
||||
}
|
||||
|
||||
li.active > a, a[aria-expanded="true"] {
|
||||
color: #fff;
|
||||
//background: #276891;
|
||||
}
|
||||
|
||||
ul a {
|
||||
font-size: 0.9em !important;
|
||||
padding-left: 30px !important;
|
||||
background: #3f3f3f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#sidebar {
|
||||
&.active {
|
||||
padding: 10px 5px;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
text-align: center;
|
||||
margin-left: 0;
|
||||
transform: none;
|
||||
|
||||
.sidebar-header {
|
||||
padding: 0px;
|
||||
|
||||
.sidebar-header-logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sidebarCollapse {
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
ul li a {
|
||||
padding: 10px;
|
||||
font-size: 0.85em;
|
||||
|
||||
i {
|
||||
margin-right: 0;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
& > ul > li > a > i {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
ul ul a {
|
||||
padding: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-toggle::after {
|
||||
top: auto;
|
||||
bottom: 10px;
|
||||
right: 50%;
|
||||
-webkit-transform: translateX(50%);
|
||||
-ms-transform: translateX(50%);
|
||||
transform: translateX(50%);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
class Admins::CompetitionSettingsController < Admins::BaseController
|
||||
def show
|
||||
@competition = current_competition
|
||||
end
|
||||
|
||||
def update
|
||||
Admins::SaveLaboratorySettingService.call(current_competition, form_params)
|
||||
render_ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_competition
|
||||
@_current_competition ||= Competition.find(params[:competition_id])
|
||||
end
|
||||
|
||||
def form_params
|
||||
params.permit(:identifier, :name, :nav_logo, :login_logo, :tab_logo, :footer, navbar: %i[name link hidden])
|
||||
end
|
||||
end
|
@ -0,0 +1,46 @@
|
||||
class Admins::CompetitionsController < Admins::BaseController
|
||||
include CustomSortable
|
||||
before_action :find_competition, except: [:index]
|
||||
|
||||
def index
|
||||
params[:sort_by] = params[:sort_by].presence || 'created_on'
|
||||
params[:sort_direction] = params[:sort_direction].presence || 'desc'
|
||||
@competitions = custom_sort Competition.all, params[:sort_by], params[:sort_direction]
|
||||
@params_page = params[:page] || 1
|
||||
@competitions = paginate @competitions
|
||||
ids = @competitions.map(&:id)
|
||||
@member_count_map = TeamMember.where(competition_id: ids).group(:competition_id).count
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def publish
|
||||
@competition.update_attributes!(:published_at, Time.now)
|
||||
end
|
||||
|
||||
def unpublish
|
||||
@competition.update_attributes!(:published_at, nil)
|
||||
end
|
||||
|
||||
def online_switch
|
||||
if @competition.status
|
||||
@competition.update_attributes!(status: false)
|
||||
else
|
||||
@competition.update_attributes!(status: true, online_time: Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
def enroll_list
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_competition
|
||||
@competition = Competition.find_by(id: params[:id])
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
class BindUsersController < ApplicationController
|
||||
before_action :require_login
|
||||
|
||||
def create
|
||||
user = CreateBindUserService.call(current_user, create_params)
|
||||
successful_authentication(user) if user.id != current_user.id
|
||||
|
||||
render_ok
|
||||
rescue ApplicationService::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
|
||||
def new_user
|
||||
current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_params
|
||||
params.permit(:username, :password, :type, :not_bind)
|
||||
end
|
||||
end
|
@ -1,4 +1,4 @@
|
||||
module Admins::ErrorRescueHandler
|
||||
module Base::ErrorRescueHandler
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
@ -1,4 +1,4 @@
|
||||
module Admins::PaginateHelper
|
||||
module Base::PaginateHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def offset
|
@ -0,0 +1,37 @@
|
||||
module Base::RenderHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def render_by_format(hash)
|
||||
format = request.format.symbol
|
||||
hash.key?(format) ? hash[format].call : hash[:html].call
|
||||
end
|
||||
|
||||
def render_forbidden
|
||||
render_by_format(html: -> { current_user&.business? ? render('shared/403') : redirect_to('/403') },
|
||||
json: -> { render status: 403, json: { messages: I18n.t('error.forbidden') } } )
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
render_by_format(html: -> { render 'shared/404' },
|
||||
js: -> { render_js_error('资源未找到') },
|
||||
json: -> { render status: 404, json: { message: '资源未找到' } })
|
||||
end
|
||||
|
||||
def render_unprocessable_entity(message, type: :alert)
|
||||
render_by_format(html: -> { render 'shared/422' },
|
||||
js: -> { render_js_error(message, type: type) },
|
||||
json: -> { render status: 422, json: { message: message } })
|
||||
end
|
||||
alias_method :render_error, :render_unprocessable_entity
|
||||
|
||||
def internal_server_error(message = '系统错误')
|
||||
@message = message
|
||||
render_by_format(html: -> { render 'shared/500' },
|
||||
js: -> { render_js_error(message) },
|
||||
json: -> { render status: 500, json: { message: message } })
|
||||
end
|
||||
|
||||
def render_js_template(template, **opts)
|
||||
render({ template: template, formats: :js }.merge(opts))
|
||||
end
|
||||
end
|
@ -0,0 +1,16 @@
|
||||
module Cooperative::RenderHelper
|
||||
include Base::RenderHelper
|
||||
|
||||
def render_delete_success
|
||||
render_js_template 'cooperative/shared/delete'
|
||||
end
|
||||
alias_method :render_success_js, :render_delete_success
|
||||
|
||||
def render_js_error(message, type: :alert)
|
||||
if type == :notify
|
||||
render js: "$.notify({ message: '#{message}' },{ type: 'danger', delay: 5000 });"
|
||||
else
|
||||
render_js_template 'cooperative/shared/error', locals: { message: message }
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,69 @@
|
||||
module LoginHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def edu_setting(name)
|
||||
EduSetting.get(name)
|
||||
end
|
||||
|
||||
def autologin_cookie_name
|
||||
edu_setting('autologin_cookie_name').presence || 'autologin'
|
||||
end
|
||||
|
||||
def set_autologin_cookie(user)
|
||||
token = Token.get_or_create_permanent_login_token(user, "autologin")
|
||||
cookie_options = {
|
||||
:value => token.value,
|
||||
:expires => 1.month.from_now,
|
||||
:path => '/',
|
||||
:secure => false,
|
||||
:httponly => true
|
||||
}
|
||||
if edu_setting('cookie_domain').present?
|
||||
cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
|
||||
end
|
||||
cookies[autologin_cookie_name] = cookie_options
|
||||
Rails.logger.info("cookies is #{cookies}")
|
||||
end
|
||||
|
||||
def successful_authentication(user)
|
||||
Rails.logger.info("id: #{user&.id} Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}")
|
||||
# Valid user
|
||||
self.logged_user = user
|
||||
|
||||
# generate a key and set cookie if autologin
|
||||
set_autologin_cookie(user)
|
||||
|
||||
UserAction.create(action_id: user&.id, action_type: 'Login', user_id: user&.id, ip: request.remote_ip)
|
||||
user.update_column(:last_login_on, Time.now)
|
||||
# 注册完成后有一天的试用申请(先去掉)
|
||||
# UserDayCertification.create(user_id: user.id, status: 1)
|
||||
end
|
||||
|
||||
def logout_user
|
||||
if User.current.logged?
|
||||
if autologin = cookies.delete(autologin_cookie_name)
|
||||
User.current.delete_autologin_token(autologin)
|
||||
end
|
||||
User.current.delete_session_token(session[:tk])
|
||||
self.logged_user = nil
|
||||
end
|
||||
session[:user_id] = nil
|
||||
end
|
||||
|
||||
# Sets the logged in user
|
||||
def logged_user=(user)
|
||||
reset_session
|
||||
if user && user.is_a?(User)
|
||||
User.current = user
|
||||
start_user_session(user)
|
||||
else
|
||||
User.current = User.anonymous
|
||||
end
|
||||
end
|
||||
|
||||
def start_user_session(user)
|
||||
session[:user_id] = user.id
|
||||
session[:ctime] = Time.now.utc.to_i
|
||||
session[:atime] = Time.now.utc.to_i
|
||||
end
|
||||
end
|
@ -0,0 +1,80 @@
|
||||
class Cooperative::CarouselsController < Cooperative::BaseController
|
||||
before_action :convert_file!, only: [:create]
|
||||
|
||||
def index
|
||||
@images = current_laboratory.portal_images.order(position: :asc)
|
||||
end
|
||||
|
||||
def create
|
||||
position = current_laboratory.portal_images.count + 1
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
image = current_laboratory.portal_images.create!(create_params.merge(position: position))
|
||||
|
||||
file_path = Util::FileManage.disk_filename('PortalImage', image.id)
|
||||
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
|
||||
Util.write_file(@file, file_path)
|
||||
end
|
||||
|
||||
flash[:success] = '保存成功'
|
||||
redirect_to cooperative_carousels_path(current_laboratory)
|
||||
end
|
||||
|
||||
def update
|
||||
current_image.update!(update_params)
|
||||
render_ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
current_image.destroy!
|
||||
# 前移
|
||||
current_laboratory.portal_images.where('position > ?', current_image.position)
|
||||
.update_all('position = position - 1')
|
||||
|
||||
file_path = Util::FileManage.disk_filename('PortalImage', current_image.id)
|
||||
File.delete(file_path) if File.exist?(file_path)
|
||||
end
|
||||
render_delete_success
|
||||
end
|
||||
|
||||
def drag
|
||||
move = current_laboratory.portal_images.find_by(id: params[:move_id])
|
||||
after = current_laboratory.portal_images.find_by(id: params[:after_id])
|
||||
|
||||
Admins::DragPortalImageService.call(current_laboratory, move, after)
|
||||
render_ok
|
||||
rescue Admins::DragPortalImageService::Error => e
|
||||
render_error(e.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_image
|
||||
@_current_image ||= current_laboratory.portal_images.find(params[:id])
|
||||
end
|
||||
|
||||
def create_params
|
||||
params.require(:portal_image).permit(:name, :link)
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.permit(:name, :link, :status)
|
||||
end
|
||||
|
||||
def convert_file!
|
||||
max_size = 10 * 1024 * 1024 # 10M
|
||||
file = params.dig('portal_image', 'image')
|
||||
if file.class == ActionDispatch::Http::UploadedFile
|
||||
@file = file
|
||||
render_error('请上传文件') if @file.size.zero?
|
||||
render_error('文件大小超过限制') if @file.size > max_size
|
||||
else
|
||||
file = file.to_s.strip
|
||||
return render_error('请上传正确的图片') if file.blank?
|
||||
@file = Util.convert_base64_image(file, max_size: max_size)
|
||||
end
|
||||
rescue Base64ImageConverter::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
@ -0,0 +1,4 @@
|
||||
class Cooperative::DashboardsController < Cooperative::BaseController
|
||||
def show
|
||||
end
|
||||
end
|
@ -0,0 +1,46 @@
|
||||
class Cooperative::FilesController < Cooperative::BaseController
|
||||
before_action :convert_file!, only: [:create]
|
||||
|
||||
def create
|
||||
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
|
||||
|
||||
Util.write_file(@file, file_path)
|
||||
|
||||
render_ok(source_id: params[:source_id], source_type: params[:source_type].to_s, url: file_url + "?t=#{Random.rand}")
|
||||
rescue StandardError => ex
|
||||
logger_error(ex)
|
||||
render_error('上传失败')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def convert_file!
|
||||
max_size = 10 * 1024 * 1024 # 10M
|
||||
if params[:file].class == ActionDispatch::Http::UploadedFile
|
||||
@file = params[:file]
|
||||
render_error('请上传文件') if @file.size.zero?
|
||||
render_error('文件大小超过限制') if @file.size > max_size
|
||||
else
|
||||
file = params[:file].to_s.strip
|
||||
return render_error('请上传正确的图片') if file.blank?
|
||||
@file = Util.convert_base64_image(file, max_size: max_size)
|
||||
end
|
||||
rescue Base64ImageConverter::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
|
||||
def file_path
|
||||
@_file_path ||= begin
|
||||
case params[:source_type].to_s
|
||||
when 'Shixun' then
|
||||
Util::FileManage.disk_filename('Shixun', params[:source_id])
|
||||
else
|
||||
Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def file_url
|
||||
Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s)
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
class Cooperative::LaboratorySettingsController < Cooperative::BaseController
|
||||
def edit
|
||||
@laboratory = current_laboratory
|
||||
end
|
||||
|
||||
def update
|
||||
Admins::SaveLaboratorySettingService.call(current_laboratory, form_params)
|
||||
render_ok
|
||||
end
|
||||
|
||||
def form_params
|
||||
params.permit(:identifier, :name, :nav_logo, :login_logo, :tab_logo, :footer, navbar: %i[name link hidden])
|
||||
end
|
||||
end
|
@ -0,0 +1,23 @@
|
||||
class Cooperative::LaboratoryUsersController < Cooperative::BaseController
|
||||
def index
|
||||
laboratory_users = current_laboratory.laboratory_users
|
||||
@laboratory_users = paginate laboratory_users.includes(user: { user_extension: [:school, :department] })
|
||||
end
|
||||
|
||||
def create
|
||||
Admins::AddLaboratoryUserService.call(current_laboratory, params.permit(user_ids: []))
|
||||
render_ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
current_laboratory_user.destroy!
|
||||
|
||||
render_delete_success
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_laboratory_user
|
||||
@_current_laboratory_user ||= current_laboratory.laboratory_users.find(params[:id])
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
class Cooperative::UsersController < Cooperative::BaseController
|
||||
def index
|
||||
params[:sort_by] = params[:sort_by].presence || 'created_on'
|
||||
params[:sort_direction] = params[:sort_direction].presence || 'desc'
|
||||
params[:school_id] = current_laboratory.school_id
|
||||
|
||||
users = Admins::UserQuery.call(params)
|
||||
@users = paginate users.includes(user_extension: :school)
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
class Oauth::BaseController < ActionController::Base
|
||||
include RenderHelper
|
||||
include LoginHelper
|
||||
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
private
|
||||
|
||||
def session_user_id
|
||||
session[:user_id]
|
||||
end
|
||||
|
||||
def current_user
|
||||
@_current_user ||= User.find_by(id: session_user_id)
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
request.env['omniauth.auth']
|
||||
end
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
class Oauth::QQController < Oauth::BaseController
|
||||
def create
|
||||
user, new_user = Oauth::CreateOrFindQqAccountService.call(current_user, auth_hash)
|
||||
|
||||
successful_authentication(user)
|
||||
|
||||
render_ok(new_user: new_user)
|
||||
end
|
||||
end
|
@ -0,0 +1,11 @@
|
||||
class Oauth::WechatController < Oauth::BaseController
|
||||
def create
|
||||
user, new_user = Oauth::CreateOrFindWechatAccountService.call(current_user ,params)
|
||||
|
||||
successful_authentication(user)
|
||||
|
||||
render_ok(new_user: new_user)
|
||||
rescue Oauth::CreateOrFindWechatAccountService::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
@ -0,0 +1,13 @@
|
||||
class Users::OpenUsersController < Users::BaseAccountController
|
||||
def destroy
|
||||
current_open_users.destroy!
|
||||
|
||||
render_ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_open_users
|
||||
@_current_third_party ||= observed_user.open_users.find(params[:id])
|
||||
end
|
||||
end
|
@ -0,0 +1,34 @@
|
||||
class Weapps::BaseController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def require_wechat_login!
|
||||
return if session_unionid.present?
|
||||
|
||||
render_error('请先进行微信授权')
|
||||
end
|
||||
|
||||
def weapp_session_key
|
||||
Wechat::Weapp.session_key(session_openid)
|
||||
end
|
||||
|
||||
def set_weapp_session_key(session_key)
|
||||
Wechat::Weapp.write_session_key(session_openid, session_key)
|
||||
end
|
||||
|
||||
def session_openid
|
||||
session[:openid]
|
||||
end
|
||||
|
||||
def set_session_openid(openid)
|
||||
session[:openid] = openid
|
||||
end
|
||||
|
||||
def session_unionid
|
||||
session[:unionid]
|
||||
end
|
||||
|
||||
def set_session_unionid(unionid)
|
||||
session[:unionid] = unionid
|
||||
end
|
||||
end
|
@ -0,0 +1,26 @@
|
||||
class Weapps::CodeSessionsController < Weapps::BaseController
|
||||
def create
|
||||
logged = false
|
||||
return render_error('code不能为空') if params[:code].blank?
|
||||
|
||||
result = Wechat::Weapp.jscode2session(params[:code])
|
||||
|
||||
set_session_openid(result['openid'])
|
||||
set_weapp_session_key(result['session_key']) # weapp session_key写入缓存 后续解密需要
|
||||
|
||||
# 已授权,绑定过账号
|
||||
open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
|
||||
if open_user.present? && open_user.user
|
||||
set_session_unionid(result['unionid'])
|
||||
successful_authentication(open_user.user)
|
||||
logged = true
|
||||
else
|
||||
# 新用户
|
||||
user_info = Wechat::Weapp.decrypt(result['session_key'], params[:encrypted_data], params[:iv])
|
||||
|
||||
set_session_unionid(user_info['unionId'])
|
||||
end
|
||||
|
||||
render_ok(openid: result['openid'], logged: logged)
|
||||
end
|
||||
end
|
@ -0,0 +1,12 @@
|
||||
class Weapps::HomesController < Weapps::BaseController
|
||||
def show
|
||||
# banner图
|
||||
@images = PortalImage.where(status: true).order(position: :asc)
|
||||
|
||||
# 热门实训
|
||||
@shixuns = Shixun.where(homepage_show: true).includes(:tag_repertoires, :challenges).limit(4)
|
||||
|
||||
# 热门实践课程
|
||||
@subjects = Subject.where(homepage_show: true).includes(:shixuns, :repertoire).limit(4)
|
||||
end
|
||||
end
|
@ -0,0 +1,24 @@
|
||||
class Weapps::SessionsController < Weapps::BaseController
|
||||
before_action :require_wechat_login!
|
||||
|
||||
def create
|
||||
return render_error('重复登录') if current_user.present? && current_user.logged?
|
||||
|
||||
user = User.try_to_login(params[:login], params[:password])
|
||||
|
||||
return render_error('错误的账号或密码') if user.blank?
|
||||
return render_error('违反平台使用规范,账号已被锁定') if user.locked?
|
||||
return render_error('错误的账号或密码') unless user.check_password?(params[:password].to_s)
|
||||
|
||||
if user.wechat_open_user && user.wechat_open_user.uid != session_unionid
|
||||
render_error('该账号已被其它微信号绑定')
|
||||
return
|
||||
end
|
||||
|
||||
# 绑定微信号
|
||||
OpenUsers::Wechat.create!(user: user, uid: session_unionid) if user.wechat_open_user.blank?
|
||||
|
||||
successful_authentication(user)
|
||||
render_ok
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
class Weapps::VerifiesController < Weapps::BaseController
|
||||
before_action :require_wechat_login!
|
||||
|
||||
def create
|
||||
valid = Wechat::Weapp.verify?(session_openid, params[:verify_string], params[:signature])
|
||||
render_ok(valid: valid)
|
||||
end
|
||||
end
|
@ -1,110 +1,3 @@
|
||||
module Admins::BaseHelper
|
||||
def sidebar_item_group(url, text, **opts)
|
||||
link_opts = url.start_with?('/') ? {} : { 'data-toggle': 'collapse', 'aria-expanded': false }
|
||||
content =
|
||||
link_to url, link_opts do
|
||||
content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
|
||||
content_tag(:span, text)
|
||||
end
|
||||
|
||||
content +=
|
||||
content_tag(:ul, id: url[1..-1], class: 'collapse list-unstyled', "data-parent": '#sidebar') do
|
||||
yield
|
||||
end
|
||||
|
||||
raw content
|
||||
end
|
||||
|
||||
def sidebar_item(url, text, **opts)
|
||||
content =
|
||||
link_to url, 'data-controller': opts[:controller] do
|
||||
content_tag(:i, '', class: "fa fa-#{opts[:icon]} fa-fw", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
|
||||
content_tag(:span, text)
|
||||
end
|
||||
|
||||
raw content
|
||||
end
|
||||
|
||||
def admin_sidebar_controller
|
||||
key = params[:controller].to_s.gsub(/\//, '-')
|
||||
SidebarUtil.controller_name(key) || key
|
||||
end
|
||||
|
||||
def define_admin_breadcrumbs(&block)
|
||||
content_for(:setup_admin_breadcrumb, &block)
|
||||
end
|
||||
|
||||
def add_admin_breadcrumb(text, url = nil)
|
||||
@_admin_breadcrumbs ||= []
|
||||
@_admin_breadcrumbs << OpenStruct.new(text: text, url: url)
|
||||
end
|
||||
|
||||
def display_text(str, default = '--')
|
||||
str.presence || default
|
||||
end
|
||||
|
||||
def overflow_hidden_span(text, width: 300)
|
||||
opts = { class: 'd-inline-block text-truncate', style: "max-width: #{width}px" }
|
||||
opts.merge!('data-toggle': 'tooltip', title: text) if text != '--'
|
||||
|
||||
content_tag(:span, text, opts)
|
||||
end
|
||||
|
||||
def sort_tag(content = '', **opts)
|
||||
options = {}
|
||||
options[:sort_by] = opts.delete(:name)
|
||||
is_current_sort = params[:sort_by].to_s == options[:sort_by]
|
||||
options[:sort_direction] = is_current_sort && params[:sort_direction].to_s == 'desc' ? 'asc' : 'desc'
|
||||
|
||||
path = opts.delete(:path) + "?" + unsafe_params.merge(options).to_query
|
||||
arrow_class = case params[:sort_direction].to_s
|
||||
when 'desc' then 'fa-sort-amount-desc'
|
||||
when 'asc' then 'fa-sort-amount-asc'
|
||||
else ''
|
||||
end
|
||||
opts[:style] = "#{opts[:style]} ;position: relative;"
|
||||
|
||||
content_tag(:span, opts) do
|
||||
link_to path, remote: true do
|
||||
content = content_tag(:span) { yield } if block_given?
|
||||
|
||||
content += content_tag(:i, '', class: "fa color-light-green ml-1 #{arrow_class}", style: 'position: absolute;top:0;') if is_current_sort
|
||||
raw content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_void_link(name, **opts)
|
||||
raw link_to(name, 'javascript:void(0)', opts)
|
||||
end
|
||||
|
||||
def agree_link(name, url, **opts)
|
||||
klass = ['action agree-action', opts.delete(:class)].compact.join(' ')
|
||||
|
||||
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
|
||||
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
|
||||
|
||||
raw link_to(name, url, { method: :post, remote: true, class: klass, 'data-confirm': '确认审核通过?'}.merge(opts))
|
||||
end
|
||||
|
||||
def delete_link(name, url, **opts, &block)
|
||||
klass = ['action delete-action', opts.delete(:class)].compact.join(' ')
|
||||
|
||||
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
|
||||
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
|
||||
|
||||
if block_given?
|
||||
raw link_to(url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts), &block)
|
||||
else
|
||||
raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts))
|
||||
end
|
||||
end
|
||||
|
||||
def unsafe_params
|
||||
params.except(:controller, :action).to_unsafe_h
|
||||
end
|
||||
|
||||
def list_index_no(page,index)
|
||||
(page - 1) * 20 + index + 1
|
||||
end
|
||||
include ManageBackHelper
|
||||
end
|
@ -0,0 +1,3 @@
|
||||
module Cooperative::BaseHelper
|
||||
include ManageBackHelper
|
||||
end
|
@ -0,0 +1,115 @@
|
||||
module ManageBackHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def sidebar_item_group(url, text, **opts)
|
||||
link_opts = url.start_with?('/') ? {} : { 'data-toggle': 'collapse', 'aria-expanded': false }
|
||||
content =
|
||||
link_to url, link_opts do
|
||||
content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
|
||||
content_tag(:span, text)
|
||||
end
|
||||
|
||||
content +=
|
||||
content_tag(:ul, id: url[1..-1], class: 'collapse list-unstyled', "data-parent": '#sidebar') do
|
||||
yield
|
||||
end
|
||||
|
||||
raw content
|
||||
end
|
||||
|
||||
def sidebar_item(url, text, **opts)
|
||||
content =
|
||||
link_to url, 'data-controller': opts[:controller] do
|
||||
content_tag(:i, '', class: "fa fa-#{opts[:icon]} fa-fw", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
|
||||
content_tag(:span, text)
|
||||
end
|
||||
|
||||
raw content
|
||||
end
|
||||
|
||||
def admin_sidebar_controller
|
||||
key = params[:controller].to_s.gsub(/\//, '-')
|
||||
SidebarUtil.controller_name(key) || key
|
||||
end
|
||||
alias_method :sidebar_controller, :admin_sidebar_controller
|
||||
|
||||
def define_admin_breadcrumbs(&block)
|
||||
content_for(:setup_admin_breadcrumb, &block)
|
||||
end
|
||||
alias_method :define_breadcrumbs, :define_admin_breadcrumbs
|
||||
|
||||
def add_admin_breadcrumb(text, url = nil)
|
||||
@_breadcrumbs ||= []
|
||||
@_breadcrumbs << OpenStruct.new(text: text, url: url)
|
||||
end
|
||||
alias_method :add_breadcrumb, :add_admin_breadcrumb
|
||||
|
||||
def display_text(str, default = '--')
|
||||
str.presence || default
|
||||
end
|
||||
|
||||
def overflow_hidden_span(text, width: 300)
|
||||
opts = { class: 'd-inline-block text-truncate', style: "max-width: #{width}px" }
|
||||
opts.merge!('data-toggle': 'tooltip', title: text) if text != '--'
|
||||
|
||||
content_tag(:span, text, opts)
|
||||
end
|
||||
|
||||
def sort_tag(content = '', **opts)
|
||||
options = {}
|
||||
options[:sort_by] = opts.delete(:name)
|
||||
is_current_sort = params[:sort_by].to_s == options[:sort_by]
|
||||
options[:sort_direction] = is_current_sort && params[:sort_direction].to_s == 'desc' ? 'asc' : 'desc'
|
||||
|
||||
path = opts.delete(:path) + "?" + unsafe_params.merge(options).to_query
|
||||
arrow_class = case params[:sort_direction].to_s
|
||||
when 'desc' then 'fa-sort-amount-desc'
|
||||
when 'asc' then 'fa-sort-amount-asc'
|
||||
else ''
|
||||
end
|
||||
opts[:style] = "#{opts[:style]} ;position: relative;"
|
||||
|
||||
content_tag(:span, opts) do
|
||||
link_to path, remote: true do
|
||||
content = content_tag(:span) { yield } if block_given?
|
||||
|
||||
content += content_tag(:i, '', class: "fa color-light-green ml-1 #{arrow_class}", style: 'position: absolute;top:0;') if is_current_sort
|
||||
raw content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_void_link(name, **opts)
|
||||
raw link_to(name, 'javascript:void(0)', opts)
|
||||
end
|
||||
|
||||
def agree_link(name, url, **opts)
|
||||
klass = ['action agree-action', opts.delete(:class)].compact.join(' ')
|
||||
|
||||
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
|
||||
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
|
||||
|
||||
raw link_to(name, url, { method: :post, remote: true, class: klass, 'data-confirm': '确认审核通过?'}.merge(opts))
|
||||
end
|
||||
|
||||
def delete_link(name, url, **opts, &block)
|
||||
klass = ['action delete-action', opts.delete(:class)].compact.join(' ')
|
||||
|
||||
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
|
||||
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
|
||||
|
||||
if block_given?
|
||||
raw link_to(url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts), &block)
|
||||
else
|
||||
raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts))
|
||||
end
|
||||
end
|
||||
|
||||
def unsafe_params
|
||||
params.except(:controller, :action).to_unsafe_h
|
||||
end
|
||||
|
||||
def list_index_no(page,index)
|
||||
(page - 1) * 20 + index + 1
|
||||
end
|
||||
end
|
@ -0,0 +1,23 @@
|
||||
# 基于Redis实现热门搜索关键字
|
||||
class HotSearchKeyword
|
||||
class << self
|
||||
def add(keyword)
|
||||
return if keyword.blank?
|
||||
Rails.cache.data.zincrby(redis_key, 1, keyword)
|
||||
end
|
||||
|
||||
def hot(limit = 5)
|
||||
Rails.cache.data.zrevrange(redis_key, 0, limit - 1)
|
||||
end
|
||||
|
||||
def available?
|
||||
Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redis_key
|
||||
'educoder:es:hot_keyword'
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,50 @@
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class QQ < OmniAuth::Strategies::OAuth2
|
||||
option :client_options, {
|
||||
site: 'https://graph.qq.com',
|
||||
authorize_url: '/oauth2.0/authorize',
|
||||
token_url: '/oauth2.0/token'
|
||||
}
|
||||
|
||||
def request_phase
|
||||
super
|
||||
end
|
||||
|
||||
def authorize_params
|
||||
super.tap do |params|
|
||||
%w[scope client_options].each do |v|
|
||||
if request.params[v]
|
||||
params[v.to_sym] = request.params[v]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uid { raw_info['openid'].to_s }
|
||||
|
||||
info do
|
||||
{
|
||||
name: user_info['nickname'],
|
||||
nickname: user_info['nickname'],
|
||||
image: user_info['figureurl_qq_1']
|
||||
}
|
||||
end
|
||||
|
||||
extra do
|
||||
{ raw_info: user_info }
|
||||
end
|
||||
|
||||
def raw_info
|
||||
access_token.options[:mode] = :query
|
||||
@raw_info ||= access_token.get('/oauth2.0/me').parsed
|
||||
end
|
||||
|
||||
def user_info
|
||||
access_token.options[:mode] = :query
|
||||
params = { oauth_consumer_key: options.client_id, openid: raw_info['openid'], format: 'json' }
|
||||
@user_info ||= access_token.get('/user/get_user_info', params: params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
class Wechat::App
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jscode2session, to: :client
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,47 @@
|
||||
class Wechat::Weapp
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jscode2session, to: :client
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
|
||||
def session_key(openid)
|
||||
Rails.cache.read(session_key_cache_key(openid))
|
||||
end
|
||||
|
||||
def write_session_key(openid, session_key)
|
||||
Rails.cache.write(session_key_cache_key(openid), session_key)
|
||||
end
|
||||
|
||||
def verify?(openid, str, signature)
|
||||
session_key = session_key(openid)
|
||||
Digest::SHA1.hexdigest("#{str}#{session_key}") == signature
|
||||
end
|
||||
|
||||
def decrypt(session_key, encrypted_data, iv)
|
||||
session_key = Base64.decode64(session_key)
|
||||
encrypted_data = Base64.decode64(encrypted_data)
|
||||
iv = Base64.decode64(iv)
|
||||
|
||||
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
|
||||
cipher.decrypt
|
||||
cipher.padding = 0
|
||||
cipher.key = session_key
|
||||
cipher.iv = iv
|
||||
data = cipher.update(encrypted_data) << cipher.final
|
||||
result = JSON.parse(data[0...-data.last.ord])
|
||||
|
||||
raise Wechat::Error, '解密错误' if result.dig('watermark', 'appid') != appid
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def session_key_cache_key(openid)
|
||||
"weapp:#{appid}:#{openid}:session_key"
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,13 @@
|
||||
module WechatOauth
|
||||
class << self
|
||||
attr_accessor :appid, :secret, :scope, :base_url
|
||||
|
||||
def logger
|
||||
@_logger ||= STDOUT
|
||||
end
|
||||
|
||||
def logger=(l)
|
||||
@_logger = l
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
class WechatOauth::Error < StandardError
|
||||
attr_reader :code
|
||||
|
||||
def initialize(code, msg)
|
||||
super(msg)
|
||||
@code = code
|
||||
end
|
||||
|
||||
def message
|
||||
I18n.t("oauth.wechat.#{code}")
|
||||
rescue I18n::MissingTranslationData
|
||||
super
|
||||
end
|
||||
end
|
@ -0,0 +1,61 @@
|
||||
module WechatOauth::Service
|
||||
module_function
|
||||
|
||||
def request(method, url, params)
|
||||
WechatOauth.logger.info("[WechatOauth] [#{method.to_s.upcase}] #{url} || #{params}")
|
||||
|
||||
client = Faraday.new(url: WechatOauth.base_url)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
WechatOauth.logger.info("[WechatOauth] [#{response.status}] #{result}")
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_s != '0'
|
||||
raise WechatOauth::Error.new(result['errcode'], result['errmsg'])
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
# response:
|
||||
# {
|
||||
# "access_token":"ACCESS_TOKEN",
|
||||
# "expires_in":7200,
|
||||
# "refresh_token":"REFRESH_TOKEN",
|
||||
# "openid":"OPENID",
|
||||
# "scope":"SCOPE",
|
||||
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
||||
# }
|
||||
def access_token(code)
|
||||
params = {
|
||||
appid: WechatOauth.appid,
|
||||
secret: WechatOauth.secret,
|
||||
code: code,
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
|
||||
request(:get, '/sns/oauth2/access_token', params)
|
||||
end
|
||||
|
||||
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
|
||||
# response:
|
||||
# {
|
||||
# "openid":"OPENID",
|
||||
# "nickname":"NICKNAME",
|
||||
# "sex":1,
|
||||
# "province":"PROVINCE",
|
||||
# "city":"CITY",
|
||||
# "country":"COUNTRY",
|
||||
# "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
|
||||
# "privilege":[
|
||||
# "PRIVILEGE1",
|
||||
# "PRIVILEGE2"
|
||||
# ],
|
||||
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
||||
#
|
||||
# }
|
||||
def user_info(access_token, openid)
|
||||
request(:get, '/sns/userinfo', access_token: access_token, openid: openid)
|
||||
end
|
||||
end
|
@ -0,0 +1,3 @@
|
||||
class CompetitionModeSetting < ApplicationRecord
|
||||
belongs_to :course
|
||||
end
|
@ -0,0 +1,11 @@
|
||||
class OpenUser < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :uid, presence: true, uniqueness: { scope: :type }
|
||||
|
||||
serialize :extra, JSON
|
||||
|
||||
def can_bind_cache_key
|
||||
"open_user:#{type}:#{uid}:can_bind"
|
||||
end
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
class OpenUsers::QQ < OpenUser
|
||||
def nickname
|
||||
extra&.[]('nickname')
|
||||
end
|
||||
|
||||
def en_type
|
||||
'qq'
|
||||
end
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
class OpenUsers::Wechat < OpenUser
|
||||
def nickname
|
||||
extra&.[]('nickname')
|
||||
end
|
||||
|
||||
def en_type
|
||||
'qq'
|
||||
end
|
||||
end
|
@ -0,0 +1,53 @@
|
||||
class CreateBindUserService < ApplicationService
|
||||
attr_reader :user, :params
|
||||
|
||||
def initialize(user, params)
|
||||
@user = user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
raise Error, '系统错误' if open_user.blank?
|
||||
raise Error, '系统错误' unless can_bind_user?
|
||||
|
||||
if params[:not_bind].to_s == 'true'
|
||||
clear_can_bind_user_flag
|
||||
return user
|
||||
end
|
||||
|
||||
bind_user = User.try_to_login(params[:username], params[:password])
|
||||
raise Error, '用户名或者密码错误' if bind_user.blank?
|
||||
raise Error, '该账号已被其他微信号绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
open_user.user_id = bind_user.id
|
||||
open_user.save!
|
||||
|
||||
user.user_extension.delete
|
||||
user.delete
|
||||
end
|
||||
|
||||
clear_can_bind_user_flag
|
||||
|
||||
bind_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def open_user
|
||||
@_open_user ||= begin
|
||||
case params[:type].to_s
|
||||
when 'wechat' then user.wechat_open_user
|
||||
when 'qq' then user.qq_open_user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def can_bind_user?
|
||||
Rails.cache.read(open_user.can_bind_cache_key).present?
|
||||
end
|
||||
|
||||
def clear_can_bind_user_flag
|
||||
Rails.cache.delete(open_user.can_bind_cache_key)
|
||||
end
|
||||
end
|
@ -0,0 +1,38 @@
|
||||
class Oauth::CreateOrFindQqAccountService < ApplicationService
|
||||
|
||||
attr_reader :user, :params
|
||||
|
||||
def initialize(user, params)
|
||||
@user = user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
new_user = false
|
||||
# 存在该用户
|
||||
open_user = OpenUsers::QQ.find_by(uid: params['uid'])
|
||||
return [open_user.user, new_user] if open_user.present?
|
||||
|
||||
if user.blank? || !user.logged?
|
||||
new_user = true
|
||||
# 新用户
|
||||
login = User.generate_login('q')
|
||||
@user = User.new(login: login, nickname: params.dig('info', 'nickname'), type: 'User', status: User::STATUS_ACTIVE)
|
||||
end
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
if user.new_record?
|
||||
user.save!
|
||||
|
||||
gender = params.dig('extra', 'raw_info', 'gender') == '女' ? 1 : 0
|
||||
user.create_user_extension!(gender: gender)
|
||||
end
|
||||
|
||||
new_open_user = OpenUsers::QQ.create!(user: user, uid: params['uid'], extra: params.dig('extra', 'raw_info'))
|
||||
|
||||
Rails.cache.write(new_open_user.can_bind_cache_key, 1, expires_in: 1.hours) if new_user # 方便后面进行账号绑定
|
||||
end
|
||||
|
||||
[user, new_user]
|
||||
end
|
||||
end
|
@ -0,0 +1,57 @@
|
||||
class Oauth::CreateOrFindWechatAccountService < ApplicationService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :user, :params
|
||||
|
||||
def initialize(user, params)
|
||||
@user = user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
code = params['code'].to_s.strip
|
||||
raise Error, 'Code不能为空' if code.blank?
|
||||
new_user = false
|
||||
|
||||
result = WechatOauth::Service.access_token(code)
|
||||
result = WechatOauth::Service.user_info(result['access_token'], result['openid'])
|
||||
|
||||
# 存在该用户
|
||||
open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
|
||||
return [open_user.user, new_user] if open_user.present?
|
||||
|
||||
if user.blank? || !user.logged?
|
||||
new_user = true
|
||||
# 新用户
|
||||
login = User.generate_login('w')
|
||||
@user = User.new(login: login, nickname: result['nickname'], type: 'User', status: User::STATUS_ACTIVE)
|
||||
end
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
if new_user
|
||||
user.save!
|
||||
|
||||
gender = result['sex'].to_i == 1 ? 0 : 1
|
||||
user.create_user_extension!(gender: gender)
|
||||
|
||||
# 下载头像
|
||||
avatar_path = Util::FileManage.source_disk_filename(user)
|
||||
Util.download_file(result['headimgurl'], avatar_path)
|
||||
end
|
||||
|
||||
new_open_user= OpenUsers::Wechat.create!(user: user, uid: result['unionid'], extra: result)
|
||||
|
||||
Rails.cache.write(new_open_user.can_bind_cache_key, 1, expires_in: 1.hours) if new_user # 方便后面进行账号绑定
|
||||
end
|
||||
|
||||
[user, new_user]
|
||||
rescue WechatOauth::Error => ex
|
||||
raise Error, ex.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def code
|
||||
params[:code].to_s.strip
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue