admin management system

dev_hs
p31729568 6 years ago
parent ad2eb895e5
commit 48138b1f7f

@ -48,13 +48,11 @@ gem 'rqrcode_png'
gem 'acts-as-taggable-on', '~> 6.0'
group :development, :test do
#group :'development.rb.example', :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails', '~> 3.8'
end
group :development do
#group :'development.rb.example' do
gem 'awesome_print'
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
@ -78,6 +76,10 @@ gem 'faraday', '~> 0.15.4'
# view
gem 'active_decorator'
gem 'bootstrap', '~> 4.3.1'
gem 'jquery-rails'
gem 'simple_form'
gem 'font-awesome-sass', '4.7.0'
# i18n
gem 'rails-i18n', '~> 5.1'

@ -59,6 +59,8 @@ GEM
archive-zip (0.11.0)
io-like (~> 0.3.0)
arel (9.0.0)
autoprefixer-rails (9.6.1)
execjs
awesome_print (1.8.0)
axlsx (3.0.0.pre)
htmlentities (~> 4.3, >= 4.3.4)
@ -71,6 +73,10 @@ GEM
bindex (0.5.0)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap (4.3.1)
autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.14.3, < 2)
sassc-rails (>= 2.0.0)
builder (3.2.3)
bulk_insert (1.7.0)
activerecord (>= 3.2.0)
@ -105,6 +111,8 @@ GEM
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
ffi (1.9.25)
font-awesome-sass (4.7.0)
sass (>= 3.2)
globalid (0.4.1)
activesupport (>= 4.2.0)
grape-entity (0.7.1)
@ -120,6 +128,10 @@ GEM
jbuilder (2.7.0)
activesupport (>= 4.2.0)
multi_json (>= 1.2)
jquery-rails (4.3.5)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jwt (2.1.0)
kaminari (1.1.1)
activesupport (>= 4.1.0)
@ -165,6 +177,7 @@ GEM
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
pdfkit (0.8.4.1)
popper_js (1.14.5)
public_suffix (3.0.2)
puma (3.12.0)
rack (2.0.5)
@ -266,6 +279,15 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sassc (2.0.1)
ffi (~> 1.9)
rake
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
searchkick (3.1.3)
activemodel (>= 4.2)
elasticsearch (>= 5)
@ -278,6 +300,9 @@ GEM
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
simple_form (4.1.0)
actionpack (>= 5.0)
activemodel (>= 5.0)
simple_xlsx_reader (1.0.4)
nokogiri
rubyzip
@ -336,14 +361,17 @@ DEPENDENCIES
axlsx (~> 3.0.0.pre)
axlsx_rails (~> 0.5.2)
bootsnap (>= 1.1.0)
bootstrap (~> 4.3.1)
bulk_insert
byebug
capybara (>= 2.15, < 4.0)
chromedriver-helper
faraday (~> 0.15.4)
font-awesome-sass (= 4.7.0)
gitlab!
grape-entity (~> 0.7.1)
jbuilder (~> 2.5)
jquery-rails
kaminari (~> 1.1, >= 1.1.1)
listen (>= 3.0.5, < 3.2)
mysql2 (>= 0.4.4, < 0.6.0)
@ -366,6 +394,7 @@ DEPENDENCIES
searchkick
selenium-webdriver
sidekiq
simple_form
simple_xlsx_reader
sinatra
spreadsheet

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1,40 @@
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery3
//= require popper
//= require bootstrap-sprockets
//= require jquery.validate.min
//= require additional-methods.min
//= require jquery-validate-message-zh
//= require bootstrap-notify
//= require jquery.cookie.min
//= require select2
//= require select2-i18n.zh-CN
//= require jquery.cxselect
//= require_tree ./admins
// ******** select2 global config ********
$.fn.select2.defaults.set('theme', 'bootstrap4');
$.fn.select2.defaults.set('language', 'zh-CN');
$(document).on('turbolinks:load', function(){
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover();
// flash alert提示框自动关闭
if($('.admin-alert-container .alert').length > 0){
setTimeout(function(){
$('.admin-alert-container .alert').alert('close');
}, 2000);
}
});
$(document).on("turbolinks:before-cache", function () {
$('[data-toggle="tooltip"]').tooltip('hide');
$('[data-toggle="popover"]').popover('hide');
});
$(function () {
});

@ -0,0 +1,156 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-users-edit-page, body.admins-users-update-page').length > 0) {
var initDepartmentSelect = true;
// ************** 学校选择 *************
var matcherFunc = function(params, data){
if ($.trim(params.term) === '') {
return data;
}
if (typeof data.text === 'undefined') {
return null;
}
if (data.name && data.name.indexOf(params.term) > -1) {
var modifiedData = $.extend({}, data, true);
return modifiedData;
}
// Return `null` if the term should not be displayed
return null;
}
var defineSchoolSelect = function (schools) {
$('.school-select').select2({
theme: 'bootstrap4',
placeholder: '查询学校/单位',
minimumInputLength: 1,
data: schools,
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
$('#user_school_id').val(item.id);
getDepartmentsData(item.id, defineDepartmentSelect2);
}
return item.name || item.text;
},
matcher: matcherFunc
});
};
var defineDepartmentSelect2 = function(departments){
departments.unshift({ id: '-1', name: '未选择' }); // 可不选
if (!initDepartmentSelect) { $('.department-select').empty(); } // 为了能够回填部门
initDepartmentSelect = false;
$('.department-select').select2({
theme: 'bootstrap4',
placeholder: '查询学院/部门',
minimumInputLength: 0,
data: departments,
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
$('#user_department_id').val(item.id);
}
return item.name || item.text;
},
matcher: matcherFunc
});
};
var getDepartmentsData = function(school_id, callback){
$.ajax({
url: '/api/schools/' + school_id + '/departments/for_option.json',
dataType: 'json',
type: 'GET',
success: function(data) {
callback(data.departments);
}
})
}
// 初始化学校选择器
$.ajax({
url: '/api/schools/for_option.json',
dataType: 'json',
type: 'GET',
success: function(data) {
defineSchoolSelect(data.schools);
}
});
// **************** 地区选择 ****************
$('.province-city-select').cxSelect({
url: '/javascripts/educoder/province-data.json',
selects: ['province-select', 'city-select']
});
// *********** 职业选择 ************
var identityData = [
{
"v": "teacher",
"n": "教师",
"s": [{"n": "教授", "v": "教授"},{"n": "副教授", "v": "副教授"},{"n": "讲师", "v": "讲师"},{"n": "助教", "v": "助教"}]
},
{
"v": "student",
"n": "学生",
"s": []
},
{
"v": "professional",
"n": "专业人士",
"s": [{"n": "企业管理者", "v": "企业管理者"},{"n": "部门管理者", "v": "部门管理者"},{"n": "高级工程师", "v": "高级工程师"},{"n": "工程师", "v": "工程师"},{"n": "助理工程师", "v": "助理工程师"}]
}
];
$('.user-identity-select').cxSelect({
data: identityData,
jsonValue: 'v',
selects: ['identity-select', 'technical-title-select']
});
$('.identity-select').on('change', function(){
if($(this).val() === 'student'){
$('.technical-title-select-wrapper').hide();
$('.form-group.user_student_id').show();
} else {
$('.technical-title-select-wrapper').show();
$('.form-group.user_student_id').hide();
}
})
var $form = $('form.edit_user')
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"user[password]": {
required: false,
minlength: 5
},
"user[password_confirmation]": {
required: false,
minlength: 5,
equalTo: "#user_password"
},
},
messages: {
"user[password_confirmation]": {
equalTo: "两次密码输入不一致"
}
}
})
$form.submit(function(e){
if(!$form.valid()){ e.preventDefault(); }
})
}
});

@ -0,0 +1,121 @@
$(document).on('turbolinks:load', function(){
if ($('body.admins-users-index-page').length > 0) {
var showSuccessNotify = function() {
$.notify({
message: '操作成功'
},{
type: 'success'
});
}
// lock user
$('.users-list-container').on('click', '.lock-action', function(){
var $lockAction = $(this);
var $unlockAction = $lockAction.siblings('.unlock-action');
var userId = $lockAction.data('id');
$.ajax({
url: '/admins/users/' + userId + '/lock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$lockAction.hide();
$unlockAction.show();
}
});
});
// unlock user
$('.users-list-container').on('click', '.unlock-action', function(){
var $unlockAction = $(this);
var $lockAction = $unlockAction.siblings('.lock-action');
var userId = $unlockAction.data('id');
$.ajax({
url: '/admins/users/' + userId + '/unlock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$lockAction.show();
$unlockAction.hide();
}
});
});
// active user
$('.users-list-container').on('click', '.active-action', function(){
var $activeAction = $(this);
var $unlockAction = $activeAction.siblings('.unlock-action');
var $lockAction = $activeAction.siblings('.lock-action');
var userId = $activeAction.data('id');
$.ajax({
url: '/admins/users/' + userId + '/unlock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$activeAction.hide();
$lockAction.show();
$unlockAction.hide();
}
});
});
// ***************** reward grade modal *****************
var $rewardGradeModal = $('.admin-users-reward-grade-modal');
var $form = $rewardGradeModal.find('form.admin-users-reward-grade-form');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
grade: {
required: true,
digits: true
},
}
});
// modal ready fire
$rewardGradeModal.on('show.bs.modal', function (event) {
var $link = $(event.relatedTarget);
var userId = $link.data('id');
$rewardGradeModal.find('.modal-body input[name="user_id"]').val(userId);
});
// modal visited fire
$rewardGradeModal.on('shown.bs.modal', function(){
$rewardGradeModal.find('.modal-body input[name="grade"]').focus();
});
$('.admin-users-reward-grade-modal .submit-btn').on('click', function(){
$form.find('.error').html('');
if ($form.valid()) {
var userId = $form.find('input[name="user_id"]').val();
$.ajax({
method: 'POST',
dataType: 'json',
url: "/admins/users/" + userId + "/reward_grade",
data: $form.serialize(),
success: function(data) {
showSuccessNotify();
$('.users-list-container .user-item-' + userId + ' td.grade-content').html(data.grade);
$rewardGradeModal.modal('hide');
},
error: function(res) {
$rewardGradeModal.find('.error').html(res.responseJSON.message);
}
});
}
});
}
});

@ -13,51 +13,7 @@
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require admin/libs/jquery/jquery-1.11.1.min.js
//= require admin/libs/bootstrap/js/bootstrap.min.js
//= require admin/libs/jqueryui/jquery-ui-1.10.4.custom.min.js
//= require admin/libs/jquery-ui-touch/jquery.ui.touch-punch.min.js
//= require admin/libs/jquery-detectmobile/detect.js
//= require admin/libs/jquery-animate-numbers/jquery.animateNumbers.js
//= require admin/libs/ios7-switch/ios7.switch.js
//= require admin/libs/fastclick/fastclick.js
//= require admin/libs/jquery-blockui/jquery.blockUI.js
//= require admin/libs/bootstrap-bootbox/bootbox.min.js
//= require admin/libs/jquery-slimscroll/jquery.slimscroll.js
//= require admin/libs/jquery-sparkline/jquery-sparkline.js
//= require admin/libs/nifty-modal/js/classie.js
//= require admin/libs/nifty-modal/js/modalEffects.js
//= require admin/libs/sortable/sortable.min.js
//= require admin/libs/bootstrap-fileinput/bootstrap.file-input.js
//= require admin/libs/bootstrap-select/bootstrap-select.min.js
//= require admin/libs/bootstrap-select2/select2.min.js
//= require admin/libs/magnific-popup/jquery.magnific-popup.min.js
//= require admin/libs/pace/pace.min.js
//= require admin/libs/bootstrap-datepicker/js/bootstrap-datepicker.js
//= require admin/libs/jquery-icheck/icheck.min.js
//= require admin/libs/prettify/prettify.js
//= require admin/js/init.js
//= require admin/libs/d3/d3.v3.js
//= require admin/libs/rickshaw/rickshaw.min.js
//= require admin/libs/raphael/raphael-min.js
//= require admin/libs/morrischart/morris.min.js
//= require admin/libs/jquery-knob/jquery.knob.js
//= require admin/libs/jquery-jvectormap/js/jquery-jvectormap-1.2.2.min.js
//= require admin/libs/jquery-jvectormap/js/jquery-jvectormap-us-aea-en.js
//= require admin/libs/jquery-clock/clock.js
//= require admin/libs/jquery-easypiechart/jquery.easypiechart.min.js
//= require admin/libs/jquery-weather/jquery.simpleWeather-2.6.min.js
//= require admin/libs/bootstrap-xeditable/js/bootstrap-editable.min.js
//= require admin/libs/bootstrap-calendar/js/bic_calendar.min.js
//= require admin/js/apps/calculator.js
//= require admin/js/apps/todo.js
//= require admin/js/apps/notes.js
//= require admin/js/pages/index.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets
//= require_tree .

@ -0,0 +1,350 @@
/*
* Project: Bootstrap Notify = v3.1.3
* Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
* Author: Mouse0270 aka Robert McIntosh
* License: MIT License
* Website: https://github.com/mouse0270/bootstrap-growl
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
// Create the defaults once
var defaults = {
element: 'body',
position: null,
type: "info",
allow_dismiss: true,
newest_on_top: false,
showProgressbar: false,
placement: {
from: "top",
align: "right"
},
offset: 20,
spacing: 10,
z_index: 1031,
delay: 1000,
timer: 1000,
url_target: '_blank',
mouse_over: null,
animate: {
enter: 'animated fadeInDown',
exit: 'animated fadeOutUp'
},
onShow: null,
onShown: null,
onClose: null,
onClosed: null,
icon_type: 'class',
template: '<div data-notify="container" class="col-xs-4 col-sm-2 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss">&times;</button><span data-notify="icon"></span> <span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>'
};
String.format = function() {
var str = arguments[0];
for (var i = 1; i < arguments.length; i++) {
str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]);
}
return str;
};
function Notify ( element, content, options ) {
// Setup Content of Notify
var content = {
content: {
message: typeof content == 'object' ? content.message : content,
title: content.title ? content.title : '',
icon: content.icon ? content.icon : '',
url: content.url ? content.url : '#',
target: content.target ? content.target : '-'
}
};
options = $.extend(true, {}, content, options);
this.settings = $.extend(true, {}, defaults, options);
this._defaults = defaults;
if (this.settings.content.target == "-") {
this.settings.content.target = this.settings.url_target;
}
this.animations = {
start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart',
end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend'
}
if (typeof this.settings.offset == 'number') {
this.settings.offset = {
x: this.settings.offset,
y: this.settings.offset
};
}
this.init();
};
$.extend(Notify.prototype, {
init: function () {
var self = this;
this.buildNotify();
if (this.settings.content.icon) {
this.setIcon();
}
if (this.settings.content.url != "#") {
this.styleURL();
}
this.placement();
this.bind();
this.notify = {
$ele: this.$ele,
update: function(command, update) {
var commands = {};
if (typeof command == "string") {
commands[command] = update;
}else{
commands = command;
}
for (var command in commands) {
switch (command) {
case "type":
this.$ele.removeClass('alert-' + self.settings.type);
this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type);
self.settings.type = commands[command];
this.$ele.addClass('alert-' + commands[command]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[command]);
break;
case "icon":
var $icon = this.$ele.find('[data-notify="icon"]');
if (self.settings.icon_type.toLowerCase() == 'class') {
$icon.removeClass(self.settings.content.icon).addClass(commands[command]);
}else{
if (!$icon.is('img')) {
$icon.find('img');
}
$icon.attr('src', commands[command]);
}
break;
case "progress":
var newDelay = self.settings.delay - (self.settings.delay * (commands[command] / 100));
this.$ele.data('notify-delay', newDelay);
this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[command]).css('width', commands[command] + '%');
break;
case "url":
this.$ele.find('[data-notify="url"]').attr('href', commands[command]);
break;
case "target":
this.$ele.find('[data-notify="url"]').attr('target', commands[command]);
break;
default:
this.$ele.find('[data-notify="' + command +'"]').html(commands[command]);
};
}
var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y);
self.reposition(posX);
},
close: function() {
self.close();
}
};
},
buildNotify: function () {
var content = this.settings.content;
this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target));
this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align);
if (!this.settings.allow_dismiss) {
this.$ele.find('[data-notify="dismiss"]').css('display', 'none');
}
if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) {
this.$ele.find('[data-notify="progressbar"]').remove();
}
},
setIcon: function() {
if (this.settings.icon_type.toLowerCase() == 'class') {
this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon);
}else{
if (this.$ele.find('[data-notify="icon"]').is('img')) {
this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon);
}else{
this.$ele.find('[data-notify="icon"]').append('<img src="'+this.settings.content.icon+'" alt="Notify Icon" />');
}
}
},
styleURL: function() {
this.$ele.find('[data-notify="url"]').css({
backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
height: '100%',
left: '0px',
position: 'absolute',
top: '0px',
width: '100%',
zIndex: this.settings.z_index + 1
});
this.$ele.find('[data-notify="dismiss"]').css({
position: 'absolute',
right: '10px',
top: '5px',
zIndex: this.settings.z_index + 2
});
},
placement: function() {
var self = this,
offsetAmt = this.settings.offset.y,
css = {
display: 'inline-block',
margin: '0px auto',
position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'),
transition: 'all .5s ease-in-out',
zIndex: this.settings.z_index
},
hasAnimation = false,
settings = this.settings;
$('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function() {
return offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing));
});
if (this.settings.newest_on_top == true) {
offsetAmt = this.settings.offset.y;
}
css[this.settings.placement.from] = offsetAmt+'px';
switch (this.settings.placement.align) {
case "left":
case "right":
css[this.settings.placement.align] = this.settings.offset.x+'px';
break;
case "center":
css.left = 0;
css.right = 0;
break;
}
this.$ele.css(css).addClass(this.settings.animate.enter);
$.each(Array('webkit', 'moz', 'o', 'ms', ''), function(index, prefix) {
self.$ele[0].style[prefix+'AnimationIterationCount'] = 1;
});
$(this.settings.element).append(this.$ele);
if (this.settings.newest_on_top == true) {
offsetAmt = (parseInt(offsetAmt)+parseInt(this.settings.spacing)) + this.$ele.outerHeight();
this.reposition(offsetAmt);
}
if ($.isFunction(self.settings.onShow)) {
self.settings.onShow.call(this.$ele);
}
this.$ele.one(this.animations.start, function(event) {
hasAnimation = true;
}).one(this.animations.end, function(event) {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
});
setTimeout(function() {
if (!hasAnimation) {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
}
}, 600);
},
bind: function() {
var self = this;
this.$ele.find('[data-notify="dismiss"]').on('click', function() {
self.close();
})
this.$ele.mouseover(function(e) {
$(this).data('data-hover', "true");
}).mouseout(function(e) {
$(this).data('data-hover', "false");
});
this.$ele.data('data-hover', "false");
if (this.settings.delay > 0) {
self.$ele.data('notify-delay', self.settings.delay);
var timer = setInterval(function() {
var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer;
if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over == "pause") || self.settings.mouse_over != "pause") {
var percent = ((self.settings.delay - delay) / self.settings.delay) * 100;
self.$ele.data('notify-delay', delay);
self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%');
}
if (delay <= -(self.settings.timer)) {
clearInterval(timer);
self.close();
}
}, self.settings.timer);
}
},
close: function() {
var self = this,
$successors = null,
posX = parseInt(this.$ele.css(this.settings.placement.from)),
hasAnimation = false;
this.$ele.data('closing', 'true').addClass(this.settings.animate.exit);
self.reposition(posX);
if ($.isFunction(self.settings.onClose)) {
self.settings.onClose.call(this.$ele);
}
this.$ele.one(this.animations.start, function(event) {
hasAnimation = true;
}).one(this.animations.end, function(event) {
$(this).remove();
if ($.isFunction(self.settings.onClosed)) {
self.settings.onClosed.call(this);
}
});
setTimeout(function() {
if (!hasAnimation) {
self.$ele.remove();
if (self.settings.onClosed) {
self.settings.onClosed(self.$ele);
}
}
}, 600);
},
reposition: function(posX) {
var self = this,
notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])',
$elements = this.$ele.nextAll(notifies);
if (this.settings.newest_on_top == true) {
$elements = this.$ele.prevAll(notifies);
}
$elements.each(function() {
$(this).css(self.settings.placement.from, posX);
posX = (parseInt(posX)+parseInt(self.settings.spacing)) + $(this).outerHeight();
});
}
});
$.notify = function ( content, options ) {
var plugin = new Notify( this, content, options );
return plugin.notify;
};
$.notifyDefaults = function( options ) {
defaults = $.extend(true, {}, defaults, options);
return defaults;
};
$.notifyClose = function( command ) {
if (typeof command === "undefined" || command == "all") {
$('[data-notify]').find('[data-notify="dismiss"]').trigger('click');
}else{
$('[data-notify-position="'+command+'"]').find('[data-notify="dismiss"]').trigger('click');
}
};
}));

File diff suppressed because one or more lines are too long

@ -0,0 +1,33 @@
(function( factory ) {
if ( typeof define === "function" && define.amd ) {
define( ["jquery", "../jquery.validate"], factory );
} else {
factory( jQuery );
}
}(function( $ ) {
/*
* Translated default messages for the jQuery validation plugin.
* Locale: ZH (Chinese, 中文 (Zhōngwén), 汉语, 漢語)
*/
$.extend($.validator.messages, {
required: "这是必填字段",
remote: "请修正此字段",
email: "请输入有效的电子邮件地址",
url: "请输入有效的网址",
date: "请输入有效的日期",
dateISO: "请输入有效的日期 (YYYY-MM-DD)",
number: "请输入有效的数字",
digits: "只能输入数字",
creditcard: "请输入有效的信用卡号码",
equalTo: "你的输入不相同",
extension: "请输入有效的后缀",
maxlength: $.validator.format("最多可以输入 {0} 个字符"),
minlength: $.validator.format("最少要输入 {0} 个字符"),
rangelength: $.validator.format("请输入长度在 {0} 到 {1} 之间的字符串"),
range: $.validator.format("请输入范围在 {0} 到 {1} 之间的数值"),
max: $.validator.format("请输入不大于 {0} 的数值"),
min: $.validator.format("请输入不小于 {0} 的数值")
});
}));

@ -0,0 +1,2 @@
/*! jquery.cookie v1.4.1 | MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});

@ -0,0 +1,403 @@
/*!
* jQuery cxSelect
* @name jquery.cxselect.js
* @version 1.4.1
* @date 2016-11-02
* @author ciaoca
* @email ciaoca@gmail.com
* @site https://github.com/ciaoca/cxSelect
* @license Released under the MIT license
*/
(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else {
factory(window.jQuery || window.Zepto || window.$);
};
}(function($) {
var cxSelect = function() {
var self = this;
var dom, settings, callback;
// 分配参数
for (var i = 0, l = arguments.length; i < l; i++) {
if (cxSelect.isJquery(arguments[i]) || cxSelect.isZepto(arguments[i])) {
dom = arguments[i];
} else if (cxSelect.isElement(arguments[i])) {
dom = $(arguments[i]);
} else if (typeof arguments[i] === 'function') {
callback = arguments[i];
} else if (typeof arguments[i] === 'object') {
settings = arguments[i];
};
};
var api = new cxSelect.init(dom, settings);
if (typeof callback === 'function') {
callback(api);
};
return api;
};
cxSelect.isElement = function(o){
if (o && (typeof HTMLElement === 'function' || typeof HTMLElement === 'object') && o instanceof HTMLElement) {
return true;
} else {
return (o && o.nodeType && o.nodeType === 1) ? true : false;
};
};
cxSelect.isJquery = function(o){
return (o && o.length && (typeof jQuery === 'function' || typeof jQuery === 'object') && o instanceof jQuery) ? true : false;
};
cxSelect.isZepto = function(o){
return (o && o.length && (typeof Zepto === 'function' || typeof Zepto === 'object') && Zepto.zepto.isZ(o)) ? true : false;
};
cxSelect.getIndex = function(n, required) {
return required ? n : n - 1;
};
cxSelect.getData = function(data, space) {
if (typeof space === 'string' && space.length) {
space = space.split('.');
for (var i = 0, l = space.length; i < l; i++) {
data = data[space[i]];
};
};
return data;
};
cxSelect.init = function(dom, settings) {
var self = this;
if (!cxSelect.isJquery(dom) && !cxSelect.isZepto(dom)) {return};
var theSelect = {
dom: {
box: dom
}
};
self.attach = cxSelect.attach.bind(theSelect);
self.detach = cxSelect.detach.bind(theSelect);
self.setOptions = cxSelect.setOptions.bind(theSelect);
self.clear = cxSelect.clear.bind(theSelect);
theSelect.changeEvent = function() {
cxSelect.selectChange.call(theSelect, this.className);
};
theSelect.settings = $.extend({}, $.cxSelect.defaults, settings, {
url: theSelect.dom.box.data('url'),
emptyStyle: theSelect.dom.box.data('emptyStyle'),
required: theSelect.dom.box.data('required'),
firstTitle: theSelect.dom.box.data('firstTitle'),
firstValue: theSelect.dom.box.data('firstValue'),
jsonSpace: theSelect.dom.box.data('jsonSpace'),
jsonName: theSelect.dom.box.data('jsonName'),
jsonValue: theSelect.dom.box.data('jsonValue'),
jsonSub: theSelect.dom.box.data('jsonSub')
});
var _dataSelects = theSelect.dom.box.data('selects');
if (typeof _dataSelects === 'string' && _dataSelects.length) {
theSelect.settings.selects = _dataSelects.split(',');
};
self.setOptions();
self.attach();
// 使用独立接口获取数据
if (!theSelect.settings.url && !theSelect.settings.data) {
cxSelect.start.apply(theSelect);
// 设置自定义数据
} else if ($.isArray(theSelect.settings.data)) {
cxSelect.start.call(theSelect, theSelect.settings.data);
// 设置 URL通过 Ajax 获取数据
} else if (typeof theSelect.settings.url === 'string' && theSelect.settings.url.length) {
$.getJSON(theSelect.settings.url, function(json) {
cxSelect.start.call(theSelect, json);
});
};
};
// 设置参数
cxSelect.setOptions = function(opts) {
var self = this;
if (opts) {
$.extend(self.settings, opts);
};
// 初次或重设选择器组
if (!$.isArray(self.selectArray) || !self.selectArray.length || (opts && opts.selects)) {
self.selectArray = [];
if ($.isArray(self.settings.selects) && self.settings.selects.length) {
var _tempSelect;
for (var i = 0, l = self.settings.selects.length; i < l; i++) {
_tempSelect = self.dom.box.find('select.' + self.settings.selects[i]);
if (!_tempSelect || !_tempSelect.length) {break};
self.selectArray.push(_tempSelect);
};
};
};
if (opts) {
if (!$.isArray(opts.data) && typeof opts.url === 'string' && opts.url.length) {
$.getJSON(self.settings.url, function(json) {
cxSelect.start.call(self, json);
});
} else {
cxSelect.start.call(self, opts.data);
};
};
};
// 绑定
cxSelect.attach = function() {
var self = this;
if (!self.attachStatus) {
self.dom.box.on('change', 'select', self.changeEvent);
};
if (typeof self.attachStatus === 'boolean') {
cxSelect.start.call(self);
};
self.attachStatus = true;
};
// 移除绑定
cxSelect.detach = function() {
var self = this;
self.dom.box.off('change', 'select', self.changeEvent);
self.attachStatus = false;
};
// 清空选项
cxSelect.clear = function(index) {
var self = this;
var _style = {
display: '',
visibility: ''
};
index = isNaN(index) ? 0 : index;
// 清空后面的 select
for (var i = index, l = self.selectArray.length; i < l; i++) {
self.selectArray[i].empty().prop('disabled', true);
if (self.settings.emptyStyle === 'none') {
_style.display = 'none';
} else if (self.settings.emptyStyle === 'hidden') {
_style.visibility = 'hidden';
};
self.selectArray[i].css(_style);
};
};
cxSelect.start = function(data) {
var self = this;
if ($.isArray(data)) {
self.settings.data = cxSelect.getData(data, self.settings.jsonSpace);
};
if (!self.selectArray.length) {return};
// 保存默认值
for (var i = 0, l = self.selectArray.length; i < l; i++) {
if (typeof self.selectArray[i].attr('data-value') !== 'string' && self.selectArray[i][0].options.length) {
self.selectArray[i].attr('data-value', self.selectArray[i].val());
};
};
if (self.settings.data || (typeof self.selectArray[0].data('url') === 'string' && self.selectArray[0].data('url').length)) {
cxSelect.getOptionData.call(self, 0);
} else {
self.selectArray[0].prop('disabled', false).css({
'display': '',
'visibility': ''
});
};
};
// 获取选项数据
cxSelect.getOptionData = function(index) {
var self = this;
if (typeof index !== 'number' || isNaN(index) || index < 0 || index >= self.selectArray.length) {return};
var _indexPrev = index - 1;
var _select = self.selectArray[index];
var _selectData;
var _valueIndex;
var _dataUrl = _select.data('url');
var _jsonSpace = typeof _select.data('jsonSpace') === 'undefined' ? self.settings.jsonSpace : _select.data('jsonSpace');
var _query = {};
var _queryName;
var _selectName;
var _selectValue;
cxSelect.clear.call(self, index);
// 使用独立接口
if (typeof _dataUrl === 'string' && _dataUrl.length) {
if (index > 0) {
for (var i = 0, j = 1; i < index; i++, j++) {
_queryName = self.selectArray[j].data('queryName');
_selectName = self.selectArray[i].attr('name');
_selectValue = self.selectArray[i].val();
if (typeof _queryName === 'string' && _queryName.length) {
_query[_queryName] = _selectValue;
} else if (typeof _selectName === 'string' && _selectName.length) {
_query[_selectName] = _selectValue;
};
};
};
$.getJSON(_dataUrl, _query, function(json) {
_selectData = cxSelect.getData(json, _jsonSpace);
cxSelect.buildOption.call(self, index, _selectData);
});
// 使用整合数据
} else if (self.settings.data && typeof self.settings.data === 'object') {
_selectData = self.settings.data;
for (var i = 0; i < index; i++) {
_valueIndex = cxSelect.getIndex(self.selectArray[i][0].selectedIndex, typeof self.selectArray[i].data('required') === 'boolean' ? self.selectArray[i].data('required') : self.settings.required);
if (typeof _selectData[_valueIndex] === 'object' && $.isArray(_selectData[_valueIndex][self.settings.jsonSub]) && _selectData[_valueIndex][self.settings.jsonSub].length) {
_selectData = _selectData[_valueIndex][self.settings.jsonSub];
} else {
_selectData = null;
break;
};
};
cxSelect.buildOption.call(self, index, _selectData);
};
};
// 构建选项列表
cxSelect.buildOption = function(index, data) {
var self = this;
var _select = self.selectArray[index];
var _required = typeof _select.data('required') === 'boolean' ? _select.data('required') : self.settings.required;
var _firstTitle = typeof _select.data('firstTitle') === 'undefined' ? self.settings.firstTitle : _select.data('firstTitle');
var _firstValue = typeof _select.data('firstValue') === 'undefined' ? self.settings.firstValue : _select.data('firstValue');
var _jsonName = typeof _select.data('jsonName') === 'undefined' ? self.settings.jsonName : _select.data('jsonName');
var _jsonValue = typeof _select.data('jsonValue') === 'undefined' ? self.settings.jsonValue : _select.data('jsonValue');
if (!$.isArray(data)) {return};
var _html = !_required ? '<option value="' + String(_firstValue) + '">' + String(_firstTitle) + '</option>' : '';
// 区分标题、值的数据
if (typeof _jsonName === 'string' && _jsonName.length) {
// 无值字段时使用标题作为值
if (typeof _jsonValue !== 'string' || !_jsonValue.length) {
_jsonValue = _jsonName;
};
for (var i = 0, l = data.length; i < l; i++) {
_html += '<option value="' + String(data[i][_jsonValue]) + '">' + String(data[i][_jsonName]) + '</option>';
};
// 数组即为值的数据
} else {
for (var i = 0, l = data.length; i < l; i++) {
_html += '<option value="' + String(data[i]) + '">' + String(data[i]) + '</option>';
};
};
_select.html(_html).prop('disabled', false).css({
'display': '',
'visibility': ''
});
// 初次加载设置默认值
if (typeof _select.attr('data-value') === 'string') {
_select.val(String(_select.attr('data-value'))).removeAttr('data-value');
if (_select[0].selectedIndex < 0) {
_select[0].options[0].selected = true;
};
};
if (_required || _select[0].selectedIndex > 0) {
_select.trigger('change');
};
};
// 改变选择时的处理
cxSelect.selectChange = function(name) {
var self = this;
if (typeof name !== 'string' || !name.length) {return};
var index;
name = name.replace(/\s+/g, ',');
name = ',' + name + ',';
// 获取当前 select 位置
for (var i = 0, l = self.selectArray.length; i < l; i++) {
if (name.indexOf(',' + self.settings.selects[i] + ',') > -1) {
index = i;
break;
};
};
if (typeof index === 'number' && index > -1) {
index += 1;
cxSelect.getOptionData.call(self, index);
};
};
$.cxSelect = function() {
return cxSelect.apply(this, arguments);
};
// 默认值
$.cxSelect.defaults = {
selects: [], // 下拉选框组
url: null, // 列表数据文件路径URL或数组数据
data: null, // 自定义数据
emptyStyle: null, // 无数据状态显示方式
required: false, // 是否为必选
firstTitle: '请选择', // 第一个选项的标题
firstValue: '', // 第一个选项的值
jsonSpace: '', // 数据命名空间
jsonName: 'n', // 数据标题字段名称
jsonValue: '', // 数据值字段名称
jsonSub: 's' // 子集数据字段名称
};
$.fn.cxSelect = function(settings, callback) {
this.each(function(i) {
$.cxSelect(this, settings, callback);
});
return this;
};
}));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,3 @@
/*! Select2 4.0.8 | https://github.com/select2/select2/blob/master/LICENSE.md */
!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,44 @@
@import "bootstrap";
@import "font-awesome-sprockets";
@import "font-awesome";
@import "select2.min";
@import "select2-bootstrap4.min";
@import "admins/*";
body {
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
display: flex;
align-items: stretch;
font-size: 14px;
background: #efefef;
}
a {
&:hover {
text-decoration: unset;
}
}
input.danger {
border-color: #dc3545!important;
}
label.error {
color: #dc3545!important;
}
.simple_form {
.form-group {
.collection_radio_buttons {
margin-bottom: 0px;
}
.form-check-inline {
height: calc(1.5em + 0.75rem + 2px)
}
}
}

@ -0,0 +1,85 @@
.admin-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 {
height: 300px;
}
}
}
}
.action-container {
.action {
padding: 0 3px;
}
}
/* 分页 */
.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 {
margin-bottom: 20px;
.search-form {
select, input {
margin-right: 10px;
font-size: 14px;
}
}
}
}

@ -0,0 +1,215 @@
#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 {
padding-left: 5px;
overflow: hidden;
margin-bottom: 10px;
}
}
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;
}
#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,36 @@
.admins-users-index-page {
.user-list-form {
}
.users-list-container {
text-align: center;
}
}
.admins-users-edit-page, .admins-users-update-page {
.user-edit-container {
.user-info {
&-content {
padding-top: 5px;
padding-bottom: 5px;
height: 80px;
}
&-name {
flex: 2;
font-size: 16px;
}
&-auth {
flex: 1;
i.fa {
margin-right: 10px;
font-size: 16px;
width: 16px;
height: 16px;
text-align: center;
}
}
}
}
}

@ -0,0 +1 @@
.select2-container--bootstrap4 .select2-selection--single{height:calc(1.5em + .75rem + 2px)!important}.select2-container--bootstrap4 .select2-selection--single .select2-selection__placeholder{color:#757575;line-height:calc(1.5em + .75rem)}.select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow{position:absolute;top:50%;right:3px;width:20px}.select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow b{top:60%;border-color:#343a40 transparent transparent;border-style:solid;border-width:5px 4px 0;width:0;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute}.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered{line-height:calc(1.5em + .75rem)}.select2-search--dropdown .select2-search__field{border:1px solid #ced4da;border-radius:.25rem}.select2-results__message{color:#6c757d}.select2-container--bootstrap4 .select2-selection--multiple{min-height:calc(1.5em + .75rem + 2px)!important}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__rendered{-webkit-box-sizing:border-box;box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice{color:#343a40;border:1px solid #bdc6d0;border-radius:.2rem;padding:0 5px 0 0;cursor:pointer;float:left;margin-top:.3em;margin-right:5px}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice__remove{color:#bdc6d0;font-weight:700;margin-left:3px;margin-right:1px;padding-right:3px;padding-left:3px;float:left}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice__remove:hover{color:#343a40}.select2-container{display:block}.select2-container :focus{outline:0}.input-group .select2-container--bootstrap4{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.input-group-prepend~.select2-container--bootstrap4 .select2-selection{border-top-left-radius:0;border-bottom-left-radius:0}.select2-container--bootstrap4 .select2-selection{border:1px solid #ced4da;border-radius:.25rem;width:100%}.select2-container--bootstrap4.select2-container--focus .select2-selection{border-color:#17a2b8;-webkit-box-shadow:0 0 0 .2rem rgba(0,123,255,.25);box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.select2-container--bootstrap4.select2-container--focus.select2-container--open .select2-selection{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--bootstrap4.select2-container--disabled.select2-container--focus .select2-selection,.select2-container--bootstrap4.select2-container--disabled .select2-selection{background-color:#e9ecef;cursor:not-allowed;border-color:#ced4da;-webkit-box-shadow:none;box-shadow:none}.select2-container--bootstrap4.select2-container--disabled.select2-container--focus .select2-search__field,.select2-container--bootstrap4.select2-container--disabled .select2-search__field{background-color:transparent}form.was-validated select:invalid~.select2-container--bootstrap4 .select2-selection,select.is-invalid~.select2-container--bootstrap4 .select2-selection{border-color:#dc3545}form.was-validated select:valid~.select2-container--bootstrap4 .select2-selection,select.is-valid~.select2-container--bootstrap4 .select2-selection{border-color:#28a745}.select2-container--bootstrap4 .select2-dropdown{border-color:#ced4da;border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--bootstrap4 .select2-dropdown.select2-dropdown--above{border-top:1px solid #ced4da;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.select2-container--bootstrap4 .select2-dropdown .select2-results__option[aria-selected=true]{background-color:#e9ecef}.select2-container--bootstrap4 .select2-results__option--highlighted,.select2-container--bootstrap4 .select2-results__option--highlighted.select2-results__option[aria-selected=true]{background-color:#007bff;color:#f8f9fa}.select2-container--bootstrap4 .select2-results__option[role=group]{padding:0}.select2-container--bootstrap4 .select2-results>.select2-results__options{max-height:15em;overflow-y:auto}.select2-container--bootstrap4 .select2-results__group{padding:6px;display:list-item;color:#6c757d}.select2-container--bootstrap4 .select2-selection__clear{width:1.2em;height:1.2em;line-height:1.15em;padding-left:.3em;margin-top:.5em;border-radius:100%;background-color:#6c757d;color:#f8f9fa;float:right;margin-right:.3em}.select2-container--bootstrap4 .select2-selection__clear:hover{background-color:#343a40}

File diff suppressed because one or more lines are too long

@ -1,3 +0,0 @@
// Place all the styles related to the users/banks controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,24 @@
class Admins::BaseController < ApplicationController
include Admins::PaginateHelper
include Admins::RenderHelper
include Admins::ErrorRescueHandler
layout 'admin'
before_action :require_login, :require_admin!
private
def require_login
return if User.current.logged?
redirect_to "/login?back_url=#{CGI::escape(request.fullpath)}"
end
def require_admin!
return if current_user.blank? || !current_user.logged?
return if current_user.admin_or_business?
render_forbidden
end
end

@ -0,0 +1,4 @@
class Admins::DashboardsController < Admins::BaseController
def index
end
end

@ -0,0 +1,62 @@
class Admins::UsersController < Admins::BaseController
def index
params[:sort_by] = params[:sort_by].presence || 'created_on'
params[:sort_direction] = params[:sort_direction].presence || 'desc'
users = Admins::UserQuery.call(params)
@users = paginate users.includes(user_extension: :school)
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
Admins::UpdateUserService.call(@user, update_params)
flash[:success] = '保存成功'
redirect_to edit_admins_user_path(@user)
rescue ActiveRecord::RecordInvalid
flash.now[:danger] = '保存失败'
render 'edit'
rescue Admins::UpdateUserService::Error => ex
flash.now[:danger] = ex.message
render 'edit'
end
def destroy
User.find(params[:id]).destroy!
render_delete_success
end
def lock
User.find(params[:user_id]).lock!
render_ok
end
def unlock
User.find(params[:user_id]).activate!
render_ok
end
def reward_grade
user = User.find(params[:user_id])
return render_unprocessable_entity('金币数量必须大于0') if params[:grade].to_i <= 0
RewardGradeService.call(user, container_id: user.id, container_type: 'Feedback', score: params[:grade].to_i, not_unique: true)
render_ok(grade: user.grade)
end
private
def update_params
params.require(:user).permit(%i[lastname nickname gender identity technical_title student_id
mail phone location location_city school_id department_id admin business is_test
password professional_certification authentication])
end
end

@ -239,7 +239,7 @@ class ApplicationController < ActionController::Base
uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
if !User.current.logged? && Rails.env.development?
User.current = User.find 1
User.current = User.find 57703
end

@ -0,0 +1,22 @@
module Admins::ErrorRescueHandler
extend ActiveSupport::Concern
included do
rescue_from Exception, Educoder::TipException do |e|
Util.logger_error e
internal_server_error
end
rescue_from ActionView::MissingTemplate, ActiveRecord::RecordNotFound, with: :render_not_found
rescue_from ActionController::ParameterMissing do
render_unprocessable_entity('参数缺失')
end
# form validation error
rescue_from ActiveModel::ValidationError do |ex|
render_unprocessable_entity(ex.model.errors.full_messages.join(','))
end
rescue_from ActiveRecord::RecordInvalid do |ex|
render_unprocessable_entity(ex.record.errors.full_messages.join(','))
end
end
end

@ -0,0 +1,15 @@
module Admins::PaginateHelper
extend ActiveSupport::Concern
def page
params[:page].to_i <= 0 ? 1 : params[:page].to_i
end
def per_page
params[:per_page].to_i <= 0 || params[:per_page].to_i > 100 ? 20 : params[:per_page].to_i
end
def paginate(relations)
relations.page(page).per(per_page)
end
end

@ -0,0 +1,47 @@
module Admins::RenderHelper
extend ActiveSupport::Concern
def render_forbidden
respond_to do |format|
format.html { redirect_to '/403' }
format.json { super }
end
end
def render_not_found
respond_to do |format|
format.html { render 'admins/shared/404' }
format.js { render_js_error('资源未找到') }
format.json { render status: 404, json: { message: '资源未找到' } }
end
end
def render_unprocessable_entity(message)
respond_to do |format|
format.html { render 'admins/shared/422' }
format.js { render_js_error(message) }
format.json { render status: 422, json: { message: message } }
end
end
alias_method :render_error, :render_unprocessable_entity
def internal_server_error
respond_to do |format|
format.html { render 'admins/shared/500' }
format.js { render_js_error(message) }
format.json { render status: 500, json: { message: '系统错误' } }
end
end
def render_js_template(template, **opts)
render({ template: template, formats: :js }.merge(opts))
end
def render_delete_success
render_js_template 'admins/shared/delete'
end
def render_js_error(message)
render_js_template 'admins/shared/error', locals: { message: message }
end
end

@ -386,6 +386,92 @@ module ApplicationHelper
m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t
end
# =========== Admin Helpers Begin ===========
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]}", '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) + "?" + params.slice(:action, :controller).merge(options).to_unsafe_h.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
content_tag(:span, opts) do
link_to path, remote: true do
content += content_tag(:i, '', class: "fa color-light-green ml-1 #{arrow_class}") 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 delete_link(name, url, **opts)
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
raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts))
end
# =========== Admin Helpers End ===========
end

@ -0,0 +1,11 @@
class SidebarUtil
class << self
def controller_name(name)
sidebar_controller_map[name]
end
def sidebar_controller_map
@_sidebar_controller_map ||= YAML.load_file(Rails.root.join('config/admins', 'sidebar.yml'))
end
end
end

@ -146,6 +146,8 @@ class User < ApplicationRecord
attr_accessor :password, :password_confirmation
delegate :gender, :department_id, :school_id, :location, :location_city, :technical_title, to: :user_extension, allow_nil: true
before_save :update_hashed_password
#
@ -232,8 +234,9 @@ class User < ApplicationRecord
user_extension&.school&.name || ''
end
def school_id
user_extension&.school_id
# 用户的学院名称
def department_name
user_extension&.department&.name || ''
end
# 课堂的老师(创建者、老师、助教)
@ -440,6 +443,10 @@ class User < ApplicationRecord
name.gsub(/\s+/, '').strip #6.11 -hs
end
def only_real_name
"#{lastname}#{firstname}"
end
# 用户是否选题毕设课题
def selected_topic?(topic)
student_graduation_topics.where(graduation_topic_id: topic.id).last.try(:status)

@ -0,0 +1,40 @@
class Admins::UserQuery < ApplicationQuery
include CustomSortable
attr_reader :params
sort_columns :created_on, :last_login_on, :experience, :grade, default_by: :created_on, default_direction: :desc
def initialize(params)
@params = params
end
def call
users = User.where(type: 'User')
# 状态
status = params[:status]
users = users.where(status: status) if status.present?
# 职业
users = users.joins(:user_extension).where(user_extensions: { identity: params[:identity] }) if params[:identity].present?
# 授权类型
if params[:auto_trial].present?
users = users.joins(user_extension: :school).where(schools: { auto_users_trial: params[:auto_trial].to_i == 1 })
end
# 关键字检索
keyword = params[:keyword].to_s.strip.presence
if keyword
sql = 'CONCAT(lastname, firstname) LIKE :keyword OR login LIKE :keyword OR mail LIKE :keyword OR phone LIKE :keyword'
users = users.where(sql, keyword: keyword)
end
# 学校名称
school_name = params[:school_name].to_s.strip.presence
users = users.joins(user_extension: :school).where('schools.name LIKE ?', "%#{school_name}%") if school_name
custom_sort(users, params[:sort_by], params[:sort_direction])
end
end

@ -0,0 +1,52 @@
class Admins::UpdateUserService < ApplicationService
Error = Class.new(StandardError)
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
user.assign_attributes(user_attributes)
user.firstname = ''
user.password = password if params[:password].present?
if params[:identity].to_s == 'student'
params[:technical_title] = nil
else
params[:student_id] = nil
end
user.user_extension.assign_attributes(user_extension_attributes)
ActiveRecord::Base.transaction do
user.save!
user.user_extension.save!
update_gitlab_password if params[:password].present?
end
user
end
private
def user_attributes
params.slice(*%i[lastname nickname mail phone admin business is_test
professional_certification authentication])
end
def user_extension_attributes
params.slice(*%i[gender identity technical_title student_id location location_city school_id department_id])
end
def update_gitlab_password
return if user.gid.blank?
# 同步修改gitlab密码
Gitlab.client.edit_user(user.gid, password: params[:password])
rescue Exception => ex
Util.logger_error(ex)
raise Error, '保存失败'
end
end

@ -1,13 +1,14 @@
class RewardGradeService < ApplicationService
attr_reader :user, :attrs
attr_reader :user, :attrs, :not_unique
def initialize(user, **attrs)
@user = user
@not_unique = attrs.delete(:not_unique) || false
@attrs = attrs.slice(*%i[container_id container_type score])
end
def call
return if user.grades.exists?(attrs)
return if user.grades.exists?(attrs) && !not_unique
ActiveRecord::Base.transaction do
grade = user.grades.create!(attrs)

@ -0,0 +1,188 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('概览', admins_path) %>
<% end %>
<div class="header bg-gradient-primary pb-8 pt-md-8">
<div class="container-fluid">
<div class="header-body">
<!-- Card stats -->
<div class="row">
<div class="col-xl-3 col-lg-6">
<div class="card card-stats mb-4 mb-xl-0">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">Traffic</h5>
<span class="h2 font-weight-bold mb-0">350,897</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-danger text-white rounded-circle shadow">
<i class="fas fa-pie-chart"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-muted text-sm">
<span class="text-success mr-2"><i class="fa fa-arrow-up"></i> 3.48%</span>
<span class="text-nowrap">Since last month</span>
</p>
</div>
</div>
</div>
<div class="col-xl-3 col-lg-6">
<div class="card card-stats mb-4 mb-xl-0">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">New users</h5>
<span class="h2 font-weight-bold mb-0">2,356</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-warning text-white rounded-circle shadow">
<i class="fas fa-pie-chart"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-muted text-sm">
<span class="text-danger mr-2"><i class="fas fa-arrow-down"></i> 3.48%</span>
<span class="text-nowrap">Since last week</span>
</p>
</div>
</div>
</div>
<div class="col-xl-3 col-lg-6">
<div class="card card-stats mb-4 mb-xl-0">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">Sales</h5>
<span class="h2 font-weight-bold mb-0">924</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-yellow text-white rounded-circle shadow">
<i class="fas fa-user"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-muted text-sm">
<span class="text-warning mr-2"><i class="fas fa-arrow-down"></i> 1.10%</span>
<span class="text-nowrap">Since yesterday</span>
</p>
</div>
</div>
</div>
<div class="col-xl-3 col-lg-6">
<div class="card card-stats mb-4 mb-xl-0">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">Performance</h5>
<span class="h2 font-weight-bold mb-0">49,65%</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-info text-white rounded-circle shadow">
<i class="fas fa-pie-chart"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-muted text-sm">
<span class="text-success mr-2"><i class="fas fa-arrow-up"></i> 12%</span>
<span class="text-nowrap">Since last month</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid mt--7">
<div class="row mt-5">
<div class="col-xl-8 mb-5 mb-xl-0">
<div class="card shadow">
<div class="card-header border-0">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0">Page visits</h3>
</div>
<div class="col text-right">
<a href="#!" class="btn btn-sm btn-primary">Test</a>
</div>
</div>
</div>
<div class="table-responsive">
<!-- Projects table -->
<table class="table align-items-center table-flush">
<thead class="thead-light">
<tr>
<th scope="col">Test</th>
<th scope="col">Test</th>
<th scope="col">Test</th>
<th scope="col">Test</th>
</tr>
</thead>
<tbody>
<% 5.times do %>
<tr>
<th scope="row">/test/</th>
<td>4,569</td>
<td>340</td>
<td>
<i class="fas fa-arrow-up text-success mr-3"></i> 46,53%
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-xl-4">
<div class="card shadow">
<div class="card-header border-0">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0">Test</h3>
</div>
<div class="col text-right">
<a href="#!" class="btn btn-sm btn-primary">Test</a>
</div>
</div>
</div>
<div class="table-responsive">
<!-- Projects table -->
<table class="table align-items-center table-flush">
<thead class="thead-light">
<tr>
<th scope="col">Test</th>
<th scope="col">Test</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<% 5.times do %>
<tr>
<th scope="row">
Test
</th>
<td>
1,480
</td>
<td>
<div class="d-flex align-items-center">
<span class="mr-2">60%</span>
<div>
<div class="progress">
<div class="progress-bar bg-gradient-danger" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 60%;"></div>
</div>
</div>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,11 @@
<%# Link to the "First" page
- available local variables
url: url to the first page
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
-%>
<li class="page-item first-page">
<%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote, class: 'page-link' %>
</li>

@ -0,0 +1,13 @@
<%# Non-link tag that stands for skipped pages...
- available local variables
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
-%>
<li class="page-item">
<%= link_to 'javascript:void(0)', { class: 'page-link' } do %>
<%= t('views.pagination.truncate').html_safe %>
<span class="sr-only">(current)</span>
<% end %>
</li>

@ -0,0 +1,11 @@
<%# Link to the "Last" page
- available local variables
url: url to the last page
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
-%>
<li class="page-item last-page">
<%= link_to_unless(current_page.last?, t('views.pagination.last').html_safe, url, remote: remote, class: 'page-link') %>
<li class="page-item last-page">

@ -0,0 +1,11 @@
<%# Link to the "Next" page
- available local variables
url: url to the next page
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
-%>
<li class="page-item next">
<%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote, class: 'page-link' %>
</li>

@ -0,0 +1,19 @@
<%# Link showing page number
- available local variables
page: a page object for "this" page
url: url to this page
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
-%>
<li class="page-item page<%= ' current active' if page.current? %>">
<% if page.current? %>
<%= link_to url, {remote: remote, rel: page.rel, class: 'page-link'} do %>
<%= page %>
<span class="sr-only">(current)</span>
<% end %>
<% else %>
<%= link_to page, url, {remote: remote, rel: page.rel, class: 'page-link'} %>
<% end %>
</li>

@ -0,0 +1,27 @@
<%# The container tag
- available local variables
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
paginator: the paginator that renders the pagination tags inside
-%>
<%= paginator.render do -%>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<%= first_page_tag unless current_page.first? %>
<%= prev_page_tag unless current_page.first? %>
<% each_page do |page| -%>
<% if page.display_tag? -%>
<%= page_tag page %>
<% elsif !page.was_truncated? -%>
<%= gap_tag %>
<% end -%>
<% end -%>
<% unless current_page.out_of_range? %>
<%= next_page_tag unless current_page.last? %>
<%= last_page_tag unless current_page.last? %>
<% end %>
</ul>
</nav>
<% end -%>

@ -0,0 +1,11 @@
<%# Link to the "Previous" page
- available local variables
url: url to the previous page
current_page: a page object for the currently displayed page
total_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
-%>
<li class="page-item prev">
<%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote, class: 'page-link' %>
</li>

@ -0,0 +1,6 @@
<div class="d-flex flex-column align-items-center justify-content-center not-found">
<div class="not-found-img">
<span>404</span>
</div>
<div class="not-found-text">资源未找到</div>
</div>

@ -0,0 +1,6 @@
<div class="d-flex flex-column align-items-center justify-content-center not-found">
<div class="not-found-img">
<span>422</span>
</div>
<div class="not-found-text"><%= @message %></div>
</div>

@ -0,0 +1,6 @@
<div class="d-flex flex-column align-items-center justify-content-center not-found">
<div class="not-found-img">
<span>500</span>
</div>
<div class="not-found-text">系统错误</div>
</div>

@ -0,0 +1,6 @@
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<%= message %>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>

@ -0,0 +1,13 @@
<% if @_admin_breadcrumbs.present? %>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<% @_admin_breadcrumbs&.each_with_index do |item, index| %>
<% if item.url.present? && index != @_admin_breadcrumbs.size - 1 %>
<li class="breadcrumb-item"><%= link_to item.text, item.url %></li>
<% else %>
<li class="breadcrumb-item active" aria-current="page"><%= item.text %></li>
<% end %>
<% end %>
</ol>
</nav>
<% end %>

@ -0,0 +1,20 @@
<% flash.each do |k, v| %>
<% next unless %w(success danger warning info).include?(k.to_s) %>
<div class="alert alert-<%= k %> alert-dismissible fade show" role="alert">
<%= v %>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<% end %>
<% flash.now.as_json.each do |k, v| %>
<% next unless %w(success danger warning info).include?(k.to_s) %>
<div class="alert alert-<%= k %> alert-dismissible fade show" role="alert">
<%= v %>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<% end %>

@ -0,0 +1 @@
<tr class="no-data"><td colspan="100">暂无数据</td></tr>

@ -0,0 +1,6 @@
<div class="paginate-container">
<% if objects.size.nonzero? %>
<div class="paginate-total"><%= page_entries_info objects %></div>
<% end %>
<%= paginate objects, views_prefix: 'admins', remote: true %>
</div>

@ -0,0 +1,63 @@
<% sidebar_collapse = request.cookies['admin_sidebar_collapse'].to_s == 'true' %>
<nav id="sidebar" class="<%= sidebar_collapse ? 'active' : '' %>">
<div class="sidebar-header">
<a href="/" class="sidebar-header-logo" data-toggle="tooltip" title="返回主站">
<%= image_tag('logo.png') %>
</a>
<div id="sidebarCollapse" class="navbar-btn <%= sidebar_collapse ? 'active' : '' %>">
<i class="fa fa-chevron-left fold" data-toggle="tooltip" data-placement="right" data-boundary="window" title="收起"></i>
<i class="fa fa-bars unfold" data-toggle="tooltip" data-placement="right" data-boundary="window" title="展开"></i>
</div>
</div>
<!-- Sidebar Links -->
<ul class="list-unstyled components">
<li><%= sidebar_item(admins_path, '概览', icon: 'dashboard', controller: 'admins-dashboards') %></li>
<li>
<%= sidebar_item_group('#school-submenu', '学校统计', icon: 'area-chart') do %>
<li><%= sidebar_item('#', '统计总表', icon: 'bar-chart', controller: '') %></li>
<li><%= sidebar_item('#', '数据变化报表', icon: 'line-chart', controller: '') %></li>
<% end %>
</li>
<li>
<%= sidebar_item_group('#course-submenu', '课堂+', icon: 'mortar-board') do %>
<li><%= sidebar_item('#', '课程列表', icon: 'calendar', controller: '') %></li>
<li><%= sidebar_item('#', '课堂列表', icon: 'book', controller: '') %></li>
<li><%= sidebar_item('#', '实训作业', icon: 'file-code-o', controller: '') %></li>
<li><%= sidebar_item('#', '项目列表', icon: 'sitemap', controller: '') %></li>
<% end %>
</li>
<li>
<%= sidebar_item_group('#user-submenu', '用户', icon: 'user') do %>
<li><%= sidebar_item(admins_users_path, '用户列表', icon: 'user', controller: 'admins-users') %></li>
<li><%= sidebar_item('#', '试用授权列表', icon: 'id-card-o', controller: '') %></li>
<li><%= sidebar_item('#', '自动授权列表', icon: 'id-card', controller: '') %></li>
<% end %>
</li>
<li><%= sidebar_item('/', '返回主站', icon: 'sign-out', controller: 'root') %></li>
</ul>
</nav>
<script>
$(function(){
$('#sidebarCollapse').on('click', function () {
$(this).toggleClass('active');
$('#sidebar').toggleClass('active');
$.cookie('admin_sidebar_collapse', $(this).hasClass('active'), { path: '/admins' });
});
});
$(document).on('turbolinks:load', function(){
var sidebarController = '<%= admin_sidebar_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');
}
});
</script>

@ -0,0 +1,20 @@
var deleteRow = $('<%= params[:element] %>');
var refreshUrl = '<%= params[:refresh_url] %>';
var refreshFunc = function(url) {
$.ajax({
url: url.length > 0 ? url : window.location.href,
method: 'GET',
dataType: "script"
})
}
if(deleteRow.length > 0){
var needRefresh = deleteRow.siblings().length == 0;
deleteRow.remove();
if(needRefresh){ refreshFunc(refreshUrl); }
} else {
refreshFunc(refreshUrl);
}

@ -0,0 +1,7 @@
$('.admin-alert-container').html('<%= j( render partial: 'admins/shared/alert', locals: { message: message } ) %>');
setTimeout(function() {
if ($('.admin-alert-container button.close').length > 0) {
$('.admin-alert-container button.close').trigger('click');
}
}, 2000)

@ -0,0 +1,137 @@
<%
define_admin_breadcrumbs do
add_admin_breadcrumb('用户管理', admins_users_path)
add_admin_breadcrumb('用户详情')
end
%>
<div class="box user-edit-container">
<div class="user-info mb-4 row">
<%= link_to "/users/#{@user.login}", class: 'user-info-avatar col-md-1', target: '_blank', data: { toggle: 'tooltip', title: '个人中心' } do %>
<img src="/images/<%= url_to_avatar(@user) %>" class="rounded-circle" width="80" height="80" />
<% end %>
<div class="d-flex flex-column justify-content-between col-md-3 user-info-content">
<div class="user-info-name flex"><%= @user.real_name %> | <%= @user.id %> | <%= @user.login %></div>
<div class="d-flex flex-row user-info-auth">
<% if @user.authentication? %>
<i class="fa fa-user text-success" data-toggle="tooltip" data-placement="bottom" title="已实名认证"></i>
<% elsif @user.process_real_name_apply.present? %>
<i class="fa fa-user text-danger" data-toggle="tooltip" data-placement="bottom" title="实名认证中"></i>
<% else %>
<i class="fa fa-user text-muted" data-toggle="tooltip" data-placement="bottom" title="未实名认证"></i>
<% end %>
<% if @user.professional_certification %>
<i class="fa fa-list-alt text-success" data-toggle="tooltip" data-placement="bottom" title="已职业认证"></i>
<% elsif @user.process_professional_apply.present? %>
<i class="fa fa-list-alt text-danger" data-toggle="tooltip" data-placement="bottom" title="职业认证中"></i>
<% else %>
<i class="fa fa-list-alt text-muted" data-toggle="tooltip" data-placement="bottom" title="未职业认证"></i>
<% end %>
<% if @user.phone.present? %>
<i class="fa fa-mobile text-success" data-toggle="tooltip" data-placement="bottom" title="已绑定手机"></i>
<% else %>
<i class="fa fa-mobile text-muted" data-toggle="tooltip" data-placement="bottom" title="未绑定手机"></i>
<% end %>
<% if @user.mail.present? %>
<i class="fa fa-envelope text-success" data-toggle="tooltip" data-placement="bottom" title="已绑定邮箱"></i>
<% else %>
<i class="fa fa-envelope text-muted" data-toggle="tooltip" data-placement="bottom" title="未绑定邮箱"></i>
<% end %>
</div>
<div class="user-info-last-login">最近登录:<%= @user.last_login_on&.strftime('%Y-%m-%d %H:%M') %></div>
</div>
</div>
<%= simple_form_for(@user, url: admins_user_path(@user)) do |f| %>
<div><h6>基本信息</h6></div>
<div class="form-group px-2">
<div class="form-row">
<%= f.input :lastname, label: '姓名', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-md-11', value: @user.only_real_name } %>
</div>
<div class="form-row">
<%= f.input :nickname, label: '昵称', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-md-11' } %>
<%= f.input :gender, as: :radio_buttons, label: '性别', collection: [%w(男 0), %w(女 1)], wrapper_html: { class: 'col-md-3' } %>
</div>
<div class="form-row user-identity-select">
<div class="form-group select optional col-md-1">
<%= f.label :identity, label: '职业' %>
<%= select_tag('user[identity]', [], class: 'form-control identity-select optional', 'data-value': @user.user_extension&.identity, 'data-first-title': '请选择') %>
</div>
<div class="form-group technical-title-select-wrapper optional col-md-1" style="<%= @user.user_extension.student? ? 'display:none;' : '' %>">
<%= f.label :technical_title, label: '职称' %>
<%= select_tag('user[technical_title]', [], class: 'form-control technical-title-select optional', 'data-value': @user.technical_title) %>
</div>
<%= f.input :student_id, as: :tel, label: '学号', wrapper_html: { class: 'col-md-2', style: @user.user_extension.student? ? '' : 'display:none;' }, input_html: { class: 'student-id-input' } %>
</div>
<div class="form-row">
<%= f.input :mail, as: :email, label: '邮箱地址', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %>
<%= f.input :phone, as: :tel, label: '手机号', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %>
</div>
<div class="form-row province-city-select">
<div class="form-group select optional col-md-2">
<%= f.label :location, label: '省份' %>
<%= select_tag('user[location]', [], class: 'form-control province-select optional', 'data-value': @user.location, 'data-first-title': '请选择') %>
</div>
<div class="form-group select optional col-md-2">
<%= f.label :location_city, label: '城市' %>
<%= select_tag('user[location_city]', [], class: 'form-control city-select optional', 'data-value': @user.location_city) %>
</div>
</div>
<div class="form-row">
<%= f.input :school_id, as: :hidden %>
<%= f.input :department_id, as: :hidden %>
<div class="form-group select optional col-md-2">
<%= f.label :school_name, label: '所属学校/单位' %>
<%= f.select :school_name, [@user.school_id], {}, class: 'form-control school-select optional' %>
</div>
<div class="form-group select optional col-md-2">
<%= f.label :department_name, label: '所属学院/部门' %>
<%= f.select :department_name, [@user.department_id], {}, class: 'form-control department-select optional' %>
</div>
</div>
</div>
<div class="mt-4"><h6>管理</h6></div>
<div class="form-group px-2">
<% if current_user.admin? %>
<div class="form-group check_boxes optional">
<%= f.label :role, label: '角色' %>
<div class="d-flex">
<%= f.input :admin, as: :boolean, label: '管理员', checked_value: 1, unchecked_value: 0 %>
<%= f.input :business, as: :boolean, label: '运营人员', wrapper_html: { class: 'ml-3' }, checked_value: 1, unchecked_value: 0 %>
<%= f.input :is_test, as: :boolean, label: '测试账号', wrapper_html: { class: 'ml-3' }, checked_value: 1, unchecked_value: 0 %>
</div>
</div>
<% end %>
<div class="form-group check_boxes optional">
<%= f.label :role, label: '认证信息' %>
<div class="d-flex">
<%= f.input :professional_certification, as: :boolean, label: '职业认证', checked_value: 1, unchecked_value: 0 %>
<%= f.input :authentication, as: :boolean, label: '实名认证', wrapper_html: { class: 'ml-3' }, checked_value: 1, unchecked_value: 0 %>
</div>
</div>
<div class="form-row">
<%= f.input :password, as: :password, label: '修改密码', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %>
<%= f.input :password_confirmation, as: :password, label: '确认密码', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-sm-11' } %>
</div>
</div>
<div class="form-row mt-4">
<%= f.button :submit, value: '保存', class: 'btn-primary mr-3 px-4' %>
<%= link_to '取消', 'javascript:history.go(-1)', class: 'btn btn-secondary px-4' %>
</div>
<% end %>
</div>

@ -0,0 +1,35 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('用户管理', admins_users_path) %>
<% end %>
<div class="box search-form-container user-list-form">
<%= form_tag(admins_users_path, method: :get, class: 'form-inline search-form', remote: true) do %>
<div class="form-group mr-2">
<label for="status">状态:</label>
<% status_options = [['全部', ''], ['正常', User::STATUS_ACTIVE], ['未激活', User::STATUS_REGISTERED], ['已锁定', User::STATUS_LOCKED]] %>
<%= select_tag(:status, options_for_select(status_options), class: 'form-control') %>
</div>
<div class="form-group mr-2">
<label for="identity">职业:</label>
<% identity_options = [['全部', '']] + UserExtension.identities.map { |k, v| [I18n.t("user.identity.#{k}"), v] } %>
<%= select_tag(:identity, options_for_select(identity_options), class: 'form-control') %>
</div>
<div class="form-group mr-2">
<label for="identity">授权类型:</label>
<% auto_trial_options = [['全部', ''], ['自动授权', 1], ['手动授权', 0]] %>
<%= select_tag(:auto_trial, options_for_select(auto_trial_options), class: 'form-control') %>
</div>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: 'ID/姓名/邮箱/手机号检索') %>
<%= text_field_tag(:school_name, params[:school_name], class: 'form-control col-sm-2', placeholder: '学校/单位检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3') %>
<% end %>
</div>
<div class="box users-list-container">
<%= render partial: 'admins/users/shared/user_list', locals: { users: @users } %>
</div>
<%= render partial: 'admins/users/shared/reward_grade_modal' %>

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

@ -0,0 +1,26 @@
<div class="modal fade admin-users-reward-grade-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">奖励金币</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="admin-users-reward-grade-form">
<%= hidden_field_tag(:user_id, nil) %>
<div class="form-group">
<label for="grade" class="col-form-label">金币数量:</label>
<%= number_field_tag(:grade, nil, class: 'form-control') %>
</div>
<div class="error text-danger"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,60 @@
<table class="table table-hover users-list-table">
<thead class="thead-light">
<tr>
<th width="10%">ID</th>
<th width="10%">真实姓名</th>
<th width="16%">邮件地址</th>
<th width="10%">手机号码</th>
<th width="14%">单位</th>
<th width="12%"><%= sort_tag('创建于', name: 'created_on', path: admins_users_path) %></th>
<th width="12%"><%= sort_tag('最后登录', name: 'last_login_on', path: admins_users_path) %></th>
<th width="10%"><%= sort_tag('经验值', name: 'experience', path: admins_users_path) %></th>
<th width="8%"><%= sort_tag('金币', name: 'grade', path: admins_users_path) %></th>
<th width="14%">操作</th>
</tr>
</thead>
<tbody>
<% if users.present? %>
<% users.each do |user| %>
<tr class="user-item-<%= user.id %>">
<td>
<%= link_to "/users/#{user.login}", target: '_blank' do %>
<%= overflow_hidden_span user.login, width: 100 %>
<% end %>
</td>
<td>
<%= link_to edit_admins_user_path(user) do %>
<%= overflow_hidden_span user.real_name, width: 100 %>
<% end %>
</td>
<td><%= overflow_hidden_span display_text(user.mail), width: 150 %></td>
<td><%= overflow_hidden_span display_text(user.phone), width: 100 %></td>
<td><%= overflow_hidden_span display_text(user.school_name), width: 150 %></td>
<td><%= display_text(user.created_on&.strftime('%Y-%m-%d %H:%M')) %></td>
<td><%= display_text(user.last_login_on&.strftime('%Y-%m-%d %H:%M')) %></td>
<td><%= user.experience.to_i %></td>
<td class="grade-content"><%= user.grade.to_i %></td>
<td class="action-container">
<%= javascript_void_link('奖励', class: 'action reward-grade-action', data: { toggle: 'modal', target: '.admin-users-reward-grade-modal', id: user.id }) %>
<%= javascript_void_link '解锁', class: 'action unlock-action', data: { id: user.id, confirm: '确认解锁吗?' }, style: user.locked? ? '' : 'display: none;' %>
<% if user.registered? %>
<%= javascript_void_link '激活', class: 'action active-action', data: { id: user.id, confirm: '确认激活吗?' } %>
<% end %>
<% if user.id != current_user.id %>
<%= javascript_void_link '加锁', class: 'action lock-action', data: { id: user.id, confirm: '确认加锁吗?' }, style: user.locked? || user.registered? ? 'display: none;' : '' %>
<% end %>
<%= delete_link '删除', admins_user_path(user, element: ".user-item-#{user.id}"), class: 'delete-user-action' %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: users } %>

@ -0,0 +1,6 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('用户管理', admins_users_path) %>
<% add_admin_breadcrumb('用户详情') %>
<% end %>
<h3>Users Show</h3>

@ -1,522 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>EduCoder后台管理</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<head>
<title>EduCoder后台管理</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
</head>
<%= stylesheet_link_tag 'admin', media: 'all','data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'admin', 'data-turbolinks-track': 'reload' %>
</head>
<body class="fixed-left">
<!-- Modal Start -->
<!-- Modal Task Progress -->
<div class="md-modal md-3d-flip-vertical" id="task-progress">
<div class="md-content">
<h3><strong>Task Progress</strong> Information</h3>
<div>
<p>CLEANING BUGS</p>
<div class="progress progress-xs for-modal">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 80%">
<span class="sr-only">80&#37; Complete</span>
</div>
</div>
<p>POSTING SOME STUFF</p>
<div class="progress progress-xs for-modal">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 65%">
<span class="sr-only">65&#37; Complete</span>
</div>
</div>
<p>BACKUP DATA FROM SERVER</p>
<div class="progress progress-xs for-modal">
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 95%">
<span class="sr-only">95&#37; Complete</span>
</div>
</div>
<p>RE-DESIGNING WEB APPLICATION</p>
<div class="progress progress-xs for-modal">
<div class="progress-bar progress-bar-primary" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
<span class="sr-only">100&#37; Complete</span>
</div>
</div>
<p class="text-center">
<button class="btn btn-danger btn-sm md-close">Close</button>
</p>
</div>
</div>
</div>
<!-- Modal Logout -->
<div class="md-modal md-just-me" id="logout-modal">
<div class="md-content">
<h3><strong>Logout</strong> Confirmation</h3>
<div>
<p class="text-center">Are you sure want to logout from this awesome system?</p>
<p class="text-center">
<button class="btn btn-danger md-close">Nope!</button>
<a href="login.html" class="btn btn-success md-close">Yeah, I'm sure</a>
</p>
</div>
</div>
</div> <!-- Modal End -->
<!-- Begin page -->
<div id="wrapper">
<% body_class = [params[:controller].gsub(/\//, '-'), params[:action], 'page'].join('-') %>
<body class="<%= body_class %>">
<!-- Sidebar -->
<%= render partial: 'admins/shared/sidebar' %>
<!-- Top Bar Start -->
<div class="topbar">
<div class="topbar-left">
<div class="logo">
<h1><a href="#"><img src="admin/img/logo.png" alt="Logo"></a></h1>
<!-- Page Content -->
<div class="admin-body-container">
<div class="admin-alert-container">
<%= render 'admins/shared/flash_notice' %>
</div>
<button class="button-menu-mobile open-left">
<i class="fa fa-bars"></i>
</button>
</div>
<!-- Button mobile view to collapse sidebar menu -->
<div class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-collapse2">
<ul class="nav navbar-nav hidden-xs">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="icon-th"></i></a>
<div class="dropdown-menu grid-dropdown">
<div class="row stacked">
<div class="col-xs-4">
<a href="javascript:;" data-app="notes-app" data-status="active"><i class="icon-edit"></i>Notes</a>
</div>
<div class="col-xs-4">
<a href="javascript:;" data-app="todo-app" data-status="active"><i class="icon-check"></i>Todo List</a>
</div>
<div class="col-xs-4">
<a href="javascript:;" data-app="calc" data-status="inactive"><i class="fa fa-calculator"></i>Calculator</a>
</div>
</div>
<div class="row stacked">
<div class="col-xs-4">
<a href="javascript:;" data-app="weather-widget" data-status="active"><i class="icon-cloud-3"></i>Weather</a>
</div>
<div class="col-xs-4">
<a href="javascript:;" data-app="calendar-widget2" data-status="active"><i class="icon-calendar"></i>Calendar</a>
</div>
<div class="col-xs-4">
<a href="javascript:;" data-app="stock-app" data-status="inactive"><i class="icon-chart-line"></i>Stocks</a>
</div>
</div>
<div class="clearfix"></div>
</div>
</li>
<li class="language_bar dropdown hidden-xs">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">English (US) <i class="fa fa-caret-down"></i></a>
<ul class="dropdown-menu pull-right">
<li><a href="#">German</a></li>
<li><a href="#">French</a></li>
<li><a href="#">Italian</a></li>
<li><a href="#">Spanish</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right top-navbar">
<li class="dropdown iconify hide-phone">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-globe"></i><span class="label label-danger absolute">4</span></a>
<ul class="dropdown-menu dropdown-message">
<li class="dropdown-header notif-header"><i class="icon-bell-2"></i> New Notifications<a class="pull-right" href="#"><i class="fa fa-cog"></i></a></li>
<li class="unread">
<a href="#">
<p><strong>John Doe</strong> Uploaded a photo <strong>&#34;DSC000254.jpg&#34;</strong>
<br /><i>2 minutes ago</i>
</p>
</a>
</li>
<li class="unread">
<a href="#">
<p><strong>John Doe</strong> Created an photo album <strong>&#34;Fappening&#34;</strong>
<br /><i>8 minutes ago</i>
</p>
</a>
</li>
<li>
<a href="#">
<p><strong>John Malkovich</strong> Added 3 products
<br /><i>3 hours ago</i>
</p>
</a>
</li>
<li>
<a href="#">
<p><strong>Sonata Arctica</strong> Send you a message <strong>&#34;Lorem ipsum dolor...&#34;</strong>
<br /><i>12 hours ago</i>
</p>
</a>
</li>
<li>
<a href="#">
<p><strong>Johnny Depp</strong> Updated his avatar
<br /><i>Yesterday</i>
</p>
</a>
</li>
<li class="dropdown-footer">
<div class="btn-group btn-group-justified">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary"><i class="icon-ccw-1"></i> Refresh</a>
</div>
<div class="btn-group">
<a href="#" class="btn btn-sm btn-danger"><i class="icon-trash-3"></i> Clear All</a>
</div>
<div class="btn-group">
<a href="#" class="btn btn-sm btn-success">See All <i class="icon-right-open-2"></i></a>
</div>
</div>
</li>
</ul>
</li>
<li class="dropdown iconify hide-phone">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-envelope"></i><span class="label label-danger absolute">3</span></a>
<ul class="dropdown-menu dropdown-message">
<li class="dropdown-header notif-header"><i class="icon-mail-2"></i> New Messages</li>
<li class="unread">
<a href="#" class="clearfix">
<img src="admin/images/users/chat/2.jpg" class="xs-avatar ava-dropdown" alt="Avatar">
<strong>John Doe</strong><i class="pull-right msg-time">5 minutes ago</i><br />
<p>Duis autem vel eum iriure dolor in hendrerit ...</p>
</a>
</li>
<li class="unread">
<a href="#" class="clearfix">
<img src="admin/images/users/chat/1.jpg" class="xs-avatar ava-dropdown" alt="Avatar">
<strong>Sandra Kraken</strong><i class="pull-right msg-time">22 minutes ago</i><br />
<p>Duis autem vel eum iriure dolor in hendrerit ...</p>
</a>
</li>
<li>
<a href="#" class="clearfix">
<img src="admin/images/users/chat/3.jpg" class="xs-avatar ava-dropdown" alt="Avatar">
<strong>Zoey Lombardo</strong><i class="pull-right msg-time">41 minutes ago</i><br />
<p>Duis autem vel eum iriure dolor in hendrerit ...</p>
</a>
</li>
<li class="dropdown-footer"><div class=""><a href="#" class="btn btn-sm btn-block btn-primary"><i class="fa fa-share"></i> See all messages</a></div></li>
</ul>
</li>
<li class="dropdown iconify hide-phone"><a href="#" onclick="javascript:toggle_fullscreen()"><i class="icon-resize-full-2"></i></a></li>
<li class="dropdown topbar-profile">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="rounded-image topbar-profile-image"><img src="admin/images/users/user-35.jpg"></span> Jane <strong>Doe</strong> <i class="fa fa-caret-down"></i></a>
<ul class="dropdown-menu">
<li><a href="#">My Profile</a></li>
<li><a href="#">Change Password</a></li>
<li><a href="#">Account Setting</a></li>
<li class="divider"></li>
<li><a href="#"><i class="icon-help-2"></i> Help</a></li>
<li><a href="lockscreen.html"><i class="icon-lock-1"></i> Lock me</a></li>
<li><a class="md-trigger" data-modal="logout-modal"><i class="icon-logout-1"></i> Logout</a></li>
</ul>
</li>
<li class="right-opener">
<a href="javascript:;" class="open-right"><i class="fa fa-angle-double-left"></i><i class="fa fa-angle-double-right"></i></a>
</li>
</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<!-- Top Bar End -->
<!-- Left Sidebar Start -->
<div class="left side-menu">
<div class="sidebar-inner slimscrollleft">
<!-- Search form -->
<form role="search" class="navbar-form">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control">
<button type="submit" class="btn search-button"><i class="fa fa-search"></i></button>
</div>
</form>
<div class="clearfix"></div>
<!--- Profile -->
<div class="profile-info">
<div class="col-xs-4">
<a href="profile.html" class="rounded-image profile-image"><img src="admin/images/users/user-100.jpg"></a>
</div>
<div class="col-xs-8">
<div class="profile-text">Welcome <b>Jane</b></div>
<div class="profile-buttons">
<a href="javascript:;"><i class="fa fa-envelope-o pulse"></i></a>
<a href="#connect" class="open-right"><i class="fa fa-comments"></i></a>
<a href="javascript:;" title="Sign Out"><i class="fa fa-power-off text-red-1"></i></a>
</div>
</div>
</div>
<!--- Divider -->
<div class="clearfix"></div>
<hr class="divider" />
<div class="clearfix"></div>
<!--- Divider -->
<div id="sidebar-menu">
<ul><li class='has_sub'><a href='javascript:void(0);'><i class='icon-home-3'></i><span>Dashboard</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='index.html' class='active'><span>Dashboard v1</span></a></li><li><a href='index2.html'><span>Dashboard v2</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='icon-feather'></i><span>UI Elements</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='alerts.html'><span>Alerts</span></a></li><li><a href='buttons.html'><span>Buttons</span></a></li><li><a href='calendar.html'><span>Calendar</span></a></li><li><a href='grid.html'><span>Grid</span></a></li><li><a href='icons.html'><span>Icons</span></a></li><li><a href='modals.html'><span>Modals</span></a></li><li><a href='nested-list.html'><span>Nested List</span></a></li><li><a href='notifications.html'><span>Notifications</span></a></li><li><a href='portlets.html'><span>Portlets</span></a></li><li><a href='progress-bars.html'><span>Progress Bars</span></a></li><li><a href='tabs-accordions.html'><span>Tabs & Accordions</span></a></li><li><a href='typography.html'><span>Typography</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='icon-pencil-3'></i><span>Forms</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='forms.html'><span>Form Elements</span></a></li><li><a href='advanced-forms.html'><span>Advanced Forms</span></a></li><li><a href='form-wizard.html'><span>Form Wizard</span></a></li><li><a href='form-validation.html'><span>Form Validation</span></a></li><li><a href='form-uploads.html'><span>File Uploads</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='fa fa-table'></i><span>Tables</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='tables.html'><span>Basic Tables</span></a></li><li><a href='datatables.html'><span>Datatables</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='fa fa-map-marker'></i><span>Maps</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='google-maps.html'><span>Google Maps</span></a></li><li><a href='vector-maps.html'><span>Vector Maps</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='fa fa-envelope'></i><span>Email</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='inbox.html'><span>Inbox</span></a></li><li><a href='read-message.html'><span>View Email</span></a></li><li><a href='new-message.html'><span>New Message</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='icon-chart-line'></i><span>Charts</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='sparkline-charts.html'><span>Sparkline Charts</span></a></li><li><a href='morris-charts.html'><span>Morris Charts</span></a></li><li><a href='rickshaw-charts.html'><span>Rickshaw Charts</span></a></li><li><a href='other-charts.html'><span>Other Charts</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><i class='icon-megaphone'></i><span>Extras</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='blank.html'><span>Blank Page</span></a></li><li><a href='login.html'><span>Login</span></a></li><li><a href='register.html'><span>Register</span></a></li><li><a href='lockscreen.html'><span>Lock Screen</span></a></li><li><a href='404.html'><span>404 Error</span></a></li><li><a href='500.html'><span>500 Error</span></a></li><li><a href='profile.html'><span>User Profile</span></a></li><li><a href='invoice.html'><span>Invoice</span></a></li><li><a href='gallery.html'><span>Gallery</span></a></li><li><a href='maintenance.html'><span>Maintenance</span></a></li><li class='has_sub'><a href='javascript:void(0);'><span>3 Level menu</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='javascript:void(0);'><span>Sub Item</span></a></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><span>4 Level Menu</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li class='has_sub'><a href='javascript:void(0);'><span>Sub Item - level 3</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='javascript:void(0);'><span>Sub Item - level 4</span></a></li></ul></li></ul></li><li class='has_sub'><a href='javascript:void(0);'><span>Submenu with icons</span> <span class="pull-right"><i class="fa fa-angle-down"></i></span></a><ul><li><a href='javascript:void(0);'><i class='fa fa-camera'></i><span>Item with icon</span></a></li><li><a href='javascript:void(0);'><i class='entypo entypo-users'></i><span>Another Item</span></a></li></ul></li></ul></li></ul> <div class="clearfix"></div>
</div>
<div class="clearfix"></div>
<div class="portlets">
<div id="chat_groups" class="widget transparent nomargin">
<h2>Chat Groups</h2>
<div class="widget-content">
<ul class="list-unstyled">
<li><a href="javascript:;"><i class="fa fa-circle-o text-red-1"></i> Colleagues</a></li>
<li><a href="javascript:;"><i class="fa fa-circle-o text-blue-1"></i> Family</a></li>
<li><a href="javascript:;"><i class="fa fa-circle-o text-green-1"></i> Friends</a></li>
</ul>
</div>
</div>
<div id="recent_tickets" class="widget transparent nomargin">
<h2>Recent Tickets</h2>
<div class="widget-content">
<ul class="list-unstyled">
<li>
<a href="javascript:;">My wordpress blog is broken <span>I was trying to save my page and...</span></a>
</li>
<li>
<a href="javascript:;">Server down, need help!<span>My server is not responding for the last...</span></a>
</li>
</ul>
</div>
</div>
<div class="breadcrumb-container">
<%= yield :setup_admin_breadcrumb %>
<%= render(partial: 'admins/shared/breadcrumb') %>
</div>
<div class="clearfix"></div><br><br><br>
</div>
<div class="left-footer">
<div class="progress progress-xs">
<div class="progress-bar bg-green-1" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 80%">
<span class="progress-precentage">80%</span>
</div>
<a data-toggle="tooltip" title="See task progress" class="btn btn-default md-trigger" data-modal="task-progress"><i class="fa fa-inbox"></i></a>
<div class="content">
<%= yield %>
</div>
</div>
</div>
<!-- Left Sidebar End --> <!-- Right Sidebar Start -->
<div class="right side-menu">
<ul class="nav nav-tabs nav-justified" id="right-tabs">
<li class="active"><a href="#feed" data-toggle="tab" title="Live Feed"><i class="icon-rss-2"></i></a></li>
<li><a href="#connect" data-toggle="tab" title="Chat"><i class="icon-chat"></i></a></li>
<li><a href="#settings" data-toggle="tab" title="Preferences"><i class="icon-wrench"></i></a></li>
</ul>
<div class="clearfix"></div>
<div class="tab-content">
<div class="tab-pane active" id="feed">
<div class="tab-inner slimscroller">
<div class="search-right">
<input type="text" class="form-control" placeholder="Search">
</div>
<div class="right-toolbar">
<a href="javascript:;" class="pull-right">Settings <i class="icon-cog"></i></a>
<div class="clearfix"></div>
</div>
<div class="clearfix"></div>
<div class="panel-group" id="collapse">
<div class="panel panel-default">
<div class="panel-heading bg-orange-1">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#rnotifications">
<i class="icon-bell-2"></i> Notifications
<span class="label bg-darkblue-1 pull-right">4</span>
</a>
</h4>
</div>
<div id="rnotifications" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="list-unstyled" id="notification-list">
<li><a href="javascript:;"><span class="icon-wrapper"><i class="icon-video"></i></span> 1 Video Uploaded <span class="muted">12 minutes ago</span></a></li>
<li><a href="javascript:;"><span class="icon-wrapper"><i class="icon-users-1"></i></span> 3 Users signed up <span class="muted">16 minutes ago</span></a></li>
<li><a href="javascript:;"><span class="icon-wrapper"><i class="icon-picture-1"></i></span> 1 Video Uploaded <span class="muted">12 minutes ago</span></a></li>
<li><a href="javascript:;"><span class="icon-wrapper"><i class="icon-hourglass-1"></i></span> Deadline for 1 project <span class="muted">12 minutes ago</span></a></li>
</ul>
<a class="btn btn-block btn-sm btn-warning">See all notifications</a>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading bg-green-3">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#remails">
<i class="icon-mail"></i> E-mails
<span class="label bg-darkblue-1 pull-right">3</span>
</a>
</h4>
</div>
<div id="remails" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="list-unstyled" id="inbox-list">
<li><a href="javascript:;"><span class="sender"><i class="icon-star text-yellow-2"></i> Kim Wilde</span> <span class="datetime">6 mins ago</span>
<span class="title">You keep me hangin on</span>
<span class="content">Where are you? I am waiting for 3 hours in the restaurant. I ate 3 hamburgers.</span>
</a></li>
<li><a href="javascript:;"><span class="sender">Tyler Durden</span> <span class="datetime">13 hours ago</span>
<span class="title">Buy some soap from market before</span>
<span class="content">We are crowded here. We need some more soap at home. Buy some before you come home.</span>
</a></li>
<li><a href="javascript:;"><span class="sender">John Bonomo</span> <span class="datetime">Yesterday</span>
<span class="title">Late delivery</span>
<span class="content">Hello, I ordered 15 box of viagra for a friend of mine but he still hasn't receive them.</span>
</a></li>
</ul>
<a class="btn btn-block btn-sm btn-primary">Go to inbox</a>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading bg-blue-1">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#rupdates">
<i class="icon-signal-2"></i> Updates
<span class="label bg-darkblue-1 pull-right">5</span>
</a>
</h4>
</div>
<div id="rupdates" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="list-unstyled" id="updates-list">
<li><a href="javascript:;"><span class="icon-wrapper bg-green-1"><i class="icon-comment-1"></i></span> <b>David Guetta</b> came online <abbr title="15 seconds ago">just now</abbr>.</a></li>
<li><a href="javascript:;"><span class="icon-wrapper bg-red-1"><i class="icon-user-3"></i></span> <b>Katy Perry</b> updated her profile <abbr title="4 mins ago">4 mins ago</abbr>.</a></li>
<li><a href="javascript:;"><span class="icon-wrapper bg-blue-1"><i class="icon-twitter-2"></i></span> <b>4 tweets posted</b> with cronjob <abbr title="22 mins ago">22 mins ago</abbr>.</a></li>
<li><a href="javascript:;"><span class="icon-wrapper bg-orange-3"><i class="icon-water"></i></span> <b>Adele</b> set fire to the rain <abbr title="43 mins ago">43 mins ago</abbr>.</a></li>
<li><a href="javascript:;"><span class="icon-wrapper bg-pink-2"><i class="icon-heart-broken"></i></span> <b>Taylor Swift</b> learned that you are trouble <abbr title="3 hours ago">3 days ago</abbr>.</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="connect">
<div class="tab-inner slimscroller">
<div class="search-right">
<input type="text" class="form-control" placeholder="Search">
</div>
<div class="panel-group" id="collapse">
<div class="panel panel-default" id="chat-panel">
<div class="panel-heading bg-darkblue-2">
<h4 class="panel-title">
<a data-toggle="collapse" href="#chat-coll">
<i class="icon-briefcase-1"></i> Colleagues
<span class="label bg-darkblue-1 pull-right">14</span>
</a>
</h4>
</div>
<div id="chat-coll" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="list-unstyled" id="chat-list">
<li><a href="javascript:;" class="online"><span class="chat-user-avatar"><img src="admin/images/users/chat/1.jpg"></span> <span class="chat-user-name">Dorothy Simons</span><span class="chat-user-msg">I wish I was a bird in the sky</span></a></li>
<li><a href="javascript:;" class="online"><span class="chat-user-avatar"><img src="admin/images/users/chat/2.jpg"></span> <span class="chat-user-name">John Malkovich</span><span class="chat-user-msg">You were the traitor</span></a></li>
<li><a href="javascript:;" class="online"><span class="chat-user-avatar"><img src="admin/images/users/chat/3.jpg"></span> <span class="chat-user-name">Jessica Simons</span><span class="chat-user-msg">Where is my mind</span></a></li>
<li><a href="javascript:;" class="away"><span class="chat-user-avatar"><img src="admin/images/users/chat/4.jpg"></span> <span class="chat-user-name">Jack Stallman</span><span class="chat-user-msg">Away since 13:32</span></a></li>
<li><a href="javascript:;" class="offline"><span class="chat-user-avatar"><img src="admin/images/users/chat/5.jpg"></span> <span class="chat-user-name">Neil Armstrong</span><span class="chat-user-msg" title="I am flying to the moon and back">I am flying to the moon and back</span></a></li>
<li><a href="javascript:;" class="offline"><span class="chat-user-avatar"><img src="admin/images/users/chat/6.jpg"></span> <span class="chat-user-name">Hollywood Studios</span><span class="chat-user-msg">Yes he definitely is!</span></a></li>
</ul>
</div>
</div>
</div>
<div class="panel panel-default" id="chat-panel">
<div class="panel-heading bg-darkblue-2">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">
<i class="icon-heart-3"></i> Friends
<span class="label bg-darkblue-1 pull-right">3</span>
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="list-unstyled" id="chat-list">
<li><a href="javascript:;" class="online"><span class="chat-user-avatar"><img src="admin/images/users/chat/1.jpg"></span> <span class="chat-user-name">Dorothy Simons</span><span class="chat-user-msg">I wish I was a bird in the sky</span></a></li>
<li><a href="javascript:;" class="online"><span class="chat-user-avatar"><img src="admin/images/users/chat/2.jpg"></span> <span class="chat-user-name">John Malkovich</span><span class="chat-user-msg">You were the traitor</span></a></li>
<li><a href="javascript:;" class="online"><span class="chat-user-avatar"><img src="admin/images/users/chat/3.jpg"></span> <span class="chat-user-name">Jessica Simons</span><span class="chat-user-msg" title="Eminem - The Monster ft. Rihanna"><i class="icon-play"></i> Eminem - The Monster ft. Rihanna</span></a></li>
<li><a href="javascript:;" class="away"><span class="chat-user-avatar"><img src="admin/images/users/chat/4.jpg"></span> <span class="chat-user-name">Jack Stallman</span><span class="chat-user-msg">Away since 13:32</span></a></li>
<li><a href="javascript:;" class="offline"><span class="chat-user-avatar"><img src="admin/images/users/chat/5.jpg"></span> <span class="chat-user-name">Neil Armstrong</span><span class="chat-user-msg" title="I am flying to the moon and back">I am flying to the moon and back</span></a></li>
<li><a href="javascript:;" class="offline"><span class="chat-user-avatar"><img src="admin/images/users/chat/6.jpg"></span> <span class="chat-user-name">Hollywood Studios</span><span class="chat-user-msg">Yes he definitely is!</span></a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="settings">
<div class="tab-inner slimscroller">
<div class="col-sm-12">
<h3>Preferences</h3>
<div class="row">
<div class="col-xs-8">
Live data updates
</div>
<div class="col-xs-4">
<input type="checkbox" class="ios-switch ios-switch-success ios-switch-sm" checked />
</div>
</div>
<div class="row">
<div class="col-xs-8">
Live feeds
</div>
<div class="col-xs-4">
<input type="checkbox" class="ios-switch ios-switch-success ios-switch-sm" checked />
</div>
</div>
<div class="row">
<div class="col-xs-8">
Sync data to cloud
</div>
<div class="col-xs-4">
<input type="checkbox" class="ios-switch ios-switch-success ios-switch-sm" checked />
</div>
</div>
<div class="row">
<div class="col-xs-8">
Keep activity record
</div>
<div class="col-xs-4">
<input type="checkbox" class="ios-switch ios-switch-danger ios-switch-sm" checked />
</div>
</div>
<h4>Other Settings</h4>
<div class="row">
<div class="col-xs-12">
<label class="checkboxw"><input type="checkbox" checked> Autosave settings</label>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<label class="checkboxw"><input type="checkbox"> Always online</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Right Sidebar End -->
<%= yield %>
</div>
<div id="contextMenu" class="dropdown clearfix">
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
<li><a tabindex="-1" href="javascript:;" data-priority="high"><i class="fa fa-circle-o text-red-1"></i> High Priority</a></li>
<li><a tabindex="-1" href="javascript:;" data-priority="medium"><i class="fa fa-circle-o text-orange-3"></i> Medium Priority</a></li>
<li><a tabindex="-1" href="javascript:;" data-priority="low"><i class="fa fa-circle-o text-yellow-1"></i> Low Priority</a></li>
<li><a tabindex="-1" href="javascript:;" data-priority="none"><i class="fa fa-circle-o text-lightblue-1"></i> None</a></li>
</ul>
</div>
<!-- End of page -->
<!-- the overlay modal element -->
<div class="md-overlay"></div>
<!-- End of eoverlay modal -->
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</body>
<div class="admin-modal-container"></div>
</body>
</html>

@ -0,0 +1 @@
admins-users: admins-users

@ -0,0 +1,22 @@
defaults: &defaults
access_key_id: 'test'
access_key_secret: 'test'
base_url: 'http://vod.cn-shanghai.aliyuncs.com'
cate_id: '-1'
callback_url: 'http://47.96.87.25:48080/api/callbacks/aliyun_vod.json'
signature_key: 'test12345678'
development:
<<: *defaults
access_key_id: 'LTAI4kRL1DxQPdM2'
access_key_secret: 'yiz68rxE6imziBTITggWcOeSqjUeUu'
cate_id: '1000068305'
base_url: 'http://vod.cn-shanghai.aliyuncs.com'
callback_url: 'http://47.96.87.25:48080/api/callbacks/aliyun_vod.json'
signature_key: 'sdgdfDGH14DHD5g465123'
test:
<<: *defaults
production:
<<: *defaults

@ -11,4 +11,4 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
Rails.application.config.assets.precompile += %w( admin.js admin.scss )

@ -0,0 +1,179 @@
# frozen_string_literal: true
#
# Uncomment this and change the path if necessary to include your own
# components.
# See https://github.com/plataformatec/simple_form#custom-components to know
# more about custom components.
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
#
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Wrappers are used by the form builder to generate a
# complete input. You can remove any component from the
# wrapper, change the order or even add your own to the
# stack. The options given below are used to wrap the
# whole input.
config.wrappers :default, class: :input,
hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|
## Extensions enabled by default
# Any of these extensions can be disabled for a
# given input by passing: `f.input EXTENSION_NAME => false`.
# You can make any of these extensions optional by
# renaming `b.use` to `b.optional`.
# Determines whether to use HTML5 (:email, :url, ...)
# and required attributes
b.use :html5
# Calculates placeholders automatically from I18n
# You can also pass a string as f.input placeholder: "Placeholder"
b.use :placeholder
## Optional extensions
# They are disabled unless you pass `f.input EXTENSION_NAME => true`
# to the input. If so, they will retrieve the values from the model
# if any exists. If you want to enable any of those
# extensions by default, you can change `b.optional` to `b.use`.
# Calculates maxlength from length validations for string inputs
# and/or database column lengths
b.optional :maxlength
# Calculate minlength from length validations for string inputs
b.optional :minlength
# Calculates pattern from format validations for string inputs
b.optional :pattern
# Calculates min and max from length validations for numeric inputs
b.optional :min_max
# Calculates readonly automatically from readonly attributes
b.optional :readonly
## Inputs
# b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :label_input
b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error }
## full_messages_for
# If you want to display the full error message for the attribute, you can
# use the component :full_error, like:
#
# b.use :full_error, wrap_with: { tag: :span, class: :error }
end
# The default wrapper to be used by the FormBuilder.
config.default_wrapper = :default
# Define the way to render check boxes / radio buttons with labels.
# Defaults to :nested for bootstrap config.
# inline: input + label
# nested: label > input
config.boolean_style = :nested
# Default class for buttons
config.button_class = 'btn'
# Method used to tidy up errors. Specify any Rails Array method.
# :first lists the first message for each field.
# Use :to_sentence to list all errors for each field.
# config.error_method = :first
# Default tag used for error notification helper.
config.error_notification_tag = :div
# CSS class to add for error notification helper.
config.error_notification_class = 'error_notification'
# Series of attempts to detect a default label method for collection.
# config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
# Series of attempts to detect a default value method for collection.
# config.collection_value_methods = [ :id, :to_s ]
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
# config.collection_wrapper_tag = nil
# You can define the class to use on all collection wrappers. Defaulting to none.
# config.collection_wrapper_class = nil
# You can wrap each item in a collection of radio/check boxes with a tag,
# defaulting to :span.
# config.item_wrapper_tag = :span
# You can define a class to use in all item wrappers. Defaulting to none.
# config.item_wrapper_class = nil
# How the label text should be generated altogether with the required text.
# config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" }
# You can define the class to use on all labels. Default is nil.
# config.label_class = nil
# You can define the default class to be used on forms. Can be overriden
# with `html: { :class }`. Defaulting to none.
# config.default_form_class = nil
# You can define which elements should obtain additional classes
# config.generate_additional_classes_for = [:wrapper, :label, :input]
# Whether attributes are required by default (or not). Default is true.
# config.required_by_default = true
# Tell browsers whether to use the native HTML5 validations (novalidate form option).
# These validations are enabled in SimpleForm's internal config but disabled by default
# in this configuration, which is recommended due to some quirks from different browsers.
# To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations,
# change this configuration to true.
config.browser_validations = false
# Collection of methods to detect if a file type was given.
# config.file_methods = [ :mounted_as, :file?, :public_filename, :attached? ]
# Custom mappings for input types. This should be a hash containing a regexp
# to match as key, and the input type that will be used when the field name
# matches the regexp as value.
# config.input_mappings = { /count/ => :integer }
# Custom wrappers for input types. This should be a hash containing an input
# type as key and the wrapper that will be used for all inputs with specified type.
# config.wrapper_mappings = { string: :prepend }
# Namespaces where SimpleForm should look for custom input classes that
# override default inputs.
# config.custom_inputs_namespaces << "CustomInputs"
# Default priority for time_zone inputs.
# config.time_zone_priority = nil
# Default priority for country inputs.
# config.country_priority = nil
# When false, do not use translations for labels.
# config.translate_labels = true
# Automatically discover new inputs in Rails' autoload path.
# config.inputs_discovery = true
# Cache SimpleForm inputs discovery
# config.cache_discovery = !Rails.env.development?
# Default class for inputs
# config.input_class = nil
# Define the default class of the input wrapper of the boolean input.
config.boolean_label_class = 'checkbox'
# Defines if the default input wrapper class should be included in radio
# collection wrappers.
# config.include_default_input_wrapper_class = true
# Defines which i18n scope will be used in Simple Form.
# config.i18n_scope = 'simple_form'
# Defines validation classes to the input_field. By default it's nil.
# config.input_field_valid_class = 'is-valid'
# config.input_field_error_class = 'is-invalid'
end

@ -0,0 +1,439 @@
# frozen_string_literal: true
# Please do not make direct changes to this file!
# This generator is maintained by the community around simple_form-bootstrap:
# https://github.com/rafaelfranca/simple_form-bootstrap
# All future development, tests, and organization should happen there.
# Background history: https://github.com/plataformatec/simple_form/issues/1561
# Uncomment this and change the path if necessary to include your own
# components.
# See https://github.com/plataformatec/simple_form#custom-components
# to know more about custom components.
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Default class for buttons
config.button_class = 'btn'
# Define the default class of the input wrapper of the boolean input.
config.boolean_label_class = 'form-check-label'
# How the label text should be generated altogether with the required text.
config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" }
# Define the way to render check boxes / radio buttons with labels.
config.boolean_style = :inline
# You can wrap each item in a collection of radio/check boxes with a tag
config.item_wrapper_tag = :div
# Defines if the default input wrapper class should be included in radio
# collection wrappers.
config.include_default_input_wrapper_class = false
# CSS class to add for error notification helper.
config.error_notification_class = 'alert alert-danger'
# Method used to tidy up errors. Specify any Rails Array method.
# :first lists the first message for each field.
# :to_sentence to list all errors for each field.
config.error_method = :to_sentence
# add validation classes to `input_field`
config.input_field_error_class = 'is-invalid'
config.input_field_valid_class = 'is-valid'
# vertical forms
#
# vertical default_wrapper
config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :pattern
b.optional :min_max
b.optional :readonly
b.use :label, class: 'form-control-label'
b.use :input, class: 'form-control', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# vertical input for boolean
config.wrappers :vertical_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb|
bb.use :input, class: 'form-check-input', error_class: 'is-invalid'
bb.use :label, class: 'form-check-label'
bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# vertical input for radio buttons and check boxes
config.wrappers :vertical_collection, item_wrapper_class: 'form-check', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
ba.use :label_text
end
b.use :input, class: 'form-check-input', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# vertical input for inline radio buttons and check boxes
config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
ba.use :label_text
end
b.use :input, class: 'form-check-input', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# vertical file input
config.wrappers :vertical_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :readonly
b.use :label
b.use :input, class: 'form-control-file', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# vertical multi select
config.wrappers :vertical_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'form-control-label'
b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba|
ba.use :input, class: 'form-control mx-1', error_class: 'is-invalid'
end
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# vertical range input
config.wrappers :vertical_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :readonly
b.optional :step
b.use :label
b.use :input, class: 'form-control-range', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# horizontal forms
#
# horizontal default_wrapper
config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :pattern
b.optional :min_max
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label'
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-control', error_class: 'is-invalid'
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# horizontal input for boolean
config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper tag: 'label', class: 'col-sm-3' do |ba|
ba.use :label_text
end
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |wr|
wr.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb|
bb.use :input, class: 'form-check-input', error_class: 'is-invalid'
bb.use :label, class: 'form-check-label'
bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
end
# horizontal input for radio buttons and check boxes
config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 form-control-label'
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-check-input', error_class: 'is-invalid'
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# horizontal input for inline radio buttons and check boxes
config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 form-control-label'
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-check-input', error_class: 'is-invalid'
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# horizontal file input
config.wrappers :horizontal_file, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :readonly
b.use :label, class: 'col-sm-3 form-control-label'
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|
ba.use :input, error_class: 'is-invalid'
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# horizontal multi select
config.wrappers :horizontal_multi_select, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 control-label'
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|
ba.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |bb|
bb.use :input, class: 'form-control mx-1', error_class: 'is-invalid'
end
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# horizontal range input
config.wrappers :horizontal_range, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :readonly
b.optional :step
b.use :label, class: 'col-sm-3 form-control-label'
b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-control-range', error_class: 'is-invalid'
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# inline forms
#
# inline default_wrapper
config.wrappers :inline_form, tag: 'span', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :pattern
b.optional :min_max
b.optional :readonly
b.use :label, class: 'sr-only'
b.use :input, class: 'form-control', error_class: 'is-invalid'
b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# inline input for boolean
config.wrappers :inline_boolean, tag: 'span', class: 'form-check flex-wrap justify-content-start mr-sm-2', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :input, class: 'form-check-input', error_class: 'is-invalid'
b.use :label, class: 'form-check-label'
b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# bootstrap custom forms
#
# custom input for boolean
config.wrappers :custom_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox' do |bb|
bb.use :input, class: 'custom-control-input', error_class: 'is-invalid'
bb.use :label, class: 'custom-control-label'
bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
config.wrappers :custom_boolean_switch, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox-switch' do |bb|
bb.use :input, class: 'custom-control-input', error_class: 'is-invalid'
bb.use :label, class: 'custom-control-label'
bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
# custom input for radio buttons and check boxes
config.wrappers :custom_collection, item_wrapper_class: 'custom-control', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
ba.use :label_text
end
b.use :input, class: 'custom-control-input', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# custom input for inline radio buttons and check boxes
config.wrappers :custom_collection_inline, item_wrapper_class: 'custom-control custom-control-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
ba.use :label_text
end
b.use :input, class: 'custom-control-input', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# custom file input
config.wrappers :custom_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :readonly
b.use :label, class: 'form-control-label'
b.wrapper :custom_file_wrapper, tag: 'div', class: 'custom-file' do |ba|
ba.use :input, class: 'custom-file-input', error_class: 'is-invalid'
ba.use :label, class: 'custom-file-label'
ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
end
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# custom multi select
config.wrappers :custom_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'form-control-label'
b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba|
ba.use :input, class: 'custom-select mx-1', error_class: 'is-invalid'
end
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# custom range input
config.wrappers :custom_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :readonly
b.optional :step
b.use :label, class: 'form-control-label'
b.use :input, class: 'custom-range', error_class: 'is-invalid'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# Input Group - custom component
# see example app and config at https://github.com/rafaelfranca/simple_form-bootstrap
# config.wrappers :input_group, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
# b.use :html5
# b.use :placeholder
# b.optional :maxlength
# b.optional :minlength
# b.optional :pattern
# b.optional :min_max
# b.optional :readonly
# b.use :label, class: 'form-control-label'
# b.wrapper :input_group_tag, tag: 'div', class: 'input-group' do |ba|
# ba.optional :prepend
# ba.use :input, class: 'form-control', error_class: 'is-invalid'
# ba.optional :append
# end
# b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }
# b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
# end
# Floating Labels form
#
# floating labels default_wrapper
config.wrappers :floating_labels_form, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :pattern
b.optional :min_max
b.optional :readonly
b.use :input, class: 'form-control', error_class: 'is-invalid'
b.use :label, class: 'form-control-label'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# custom multi select
config.wrappers :floating_labels_select, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
b.use :html5
b.optional :readonly
b.use :input, class: 'custom-select custom-select-lg', error_class: 'is-invalid'
b.use :label, class: 'form-control-label'
b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
# The default wrapper to be used by the FormBuilder.
config.default_wrapper = :vertical_form
# Custom wrappers for input types. This should be a hash containing an input
# type as key and the wrapper that will be used for all inputs with specified type.
config.wrapper_mappings = {
boolean: :vertical_boolean,
check_boxes: :vertical_collection_inline,
date: :vertical_multi_select,
datetime: :vertical_multi_select,
file: :vertical_file,
radio_buttons: :vertical_collection_inline,
range: :vertical_range,
time: :vertical_multi_select
}
# enable custom form wrappers
# config.wrapper_mappings = {
# boolean: :custom_boolean,
# check_boxes: :custom_collection,
# date: :custom_multi_select,
# datetime: :custom_multi_select,
# file: :custom_file,
# radio_buttons: :custom_collection,
# range: :custom_range,
# time: :custom_multi_select
# }
end

@ -0,0 +1,17 @@
zh-CN:
views:
pagination:
first: "&laquo; 首页"
last: "尾页 &raquo;"
previous: "&lsaquo; 上一页"
next: "下一页 &rsaquo;"
truncate: "&hellip;"
helpers:
page_entries_info:
one_page:
display_entries:
zero: "暂无数据"
one: "共<b>1</b>条数据"
other: "共<b>%{count}</b>条数据"
more_pages:
display_entries: "当前<b>%{first}&nbsp;-&nbsp;%{last}</b>,共<b>%{total}</b>条数据"

@ -0,0 +1,31 @@
en:
simple_form:
"yes": 'Yes'
"no": 'No'
required:
text: 'required'
mark: '*'
# You can uncomment the line below if you need to overwrite the whole required html.
# When using html, text and mark won't be used.
# html: '<abbr title="required">*</abbr>'
error_notification:
default_message: "Please review the problems below:"
# Examples
# labels:
# defaults:
# password: 'Password'
# user:
# new:
# email: 'E-mail to sign in.'
# edit:
# email: 'E-mail.'
# hints:
# defaults:
# username: 'User name to sign in.'
# password: 'No special characters, please.'
# include_blanks:
# defaults:
# age: 'Rather not say'
# prompts:
# defaults:
# age: 'Select your age'

@ -739,6 +739,19 @@ Rails.application.routes.draw do
post 'callbacks/aliyun_vod', to: 'callbacks/aliyun_vods#create'
end
namespace :admins do
get '/', to: 'dashboards#index'
resources :users, only: [:index, :edit, :update] do
member do
post :reward_grade
post :lock
post :unlock
post :active
end
end
end
#git 认证回调
match 'gitauth/*url', to: 'gits#auth', via: :all

@ -0,0 +1,15 @@
<%# frozen_string_literal: true %>
<%%= simple_form_for(@<%= singular_table_name %>) do |f| %>
<%%= f.error_notification %>
<%%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<div class="form-inputs">
<%- attributes.each do |attribute| -%>
<%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %>
<%- end -%>
</div>
<div class="form-actions">
<%%= f.button :submit %>
</div>
<%% end %>

@ -0,0 +1,254 @@
[
{
"n": "北京",
"s": [
{ "n": "东城" },
{ "n": "西城" },
{ "n": "朝阳" },
{ "n": "丰台" },
{ "n": "石景山" },
{ "n": "海淀" },
{ "n": "门头沟" },
{ "n": "房山" },
{ "n": "通州" },
{ "n": "顺义" },
{ "n": "昌平" },
{ "n": "大兴" },
{ "n": "平谷" },
{ "n": "怀柔" },
{ "n": "密云" },
{ "n": "延庆" }
]
},
{
"n": "上海",
"s": [
{ "n": "崇明" }, { "n": "黄浦" }, { "n": "卢湾" }, { "n": "徐汇" }, { "n": "长宁" }, { "n": "静安" }, { "n": "普陀" }, { "n": "闸北" }, { "n": "虹口" }, { "n": "杨浦" }, { "n": "闵行" },
{ "n": "宝山" }, { "n": "嘉定" }, { "n": "浦东" }, { "n": "金山" }, { "n": "松江" }, { "n": "青浦" }, { "n": "南汇" }, { "n": "奉贤" }
]
},
{
"n": "广东",
"s": [
{ "n": "广州" }, { "n": "深圳" }, { "n": "珠海" }, { "n": "东莞" }, { "n": "中山" }, { "n": "佛山" }, { "n": "惠州" }, { "n": "河源" }, { "n": "潮州" }, { "n": "江门" }, { "n": "揭阳" }, { "n": "茂名" },
{ "n": "梅州" }, { "n": "清远" }, { "n": "汕头" }, { "n": "汕尾" }, { "n": "韶关" }, { "n": "顺德" }, { "n": "阳江" }, { "n": "云浮" }, { "n": "湛江" }, { "n": "肇庆" }
]
},
{
"n": "江苏",
"s": [
{ "n": "南京" }, { "n": "常熟" }, { "n": "常州" }, { "n": "海门" }, { "n": "淮安" }, { "n": "江都" }, { "n": "江阴" }, { "n": "昆山" }, { "n": "连云港" }, { "n": "南通" },
{ "n": "启东" }, { "n": "沭阳" }, { "n": "宿迁" }, { "n": "苏州" }, { "n": "太仓" }, { "n": "泰州" }, { "n": "同里" }, { "n": "无锡" }, { "n": "徐州" }, { "n": "盐城" },
{ "n": "扬州" }, { "n": "宜兴" }, { "n": "仪征" }, { "n": "张家港" }, { "n": "镇江" }, { "n": "周庄" }
]
},
{
"n": "浙江",
"s": [
{ "n": "杭州" }, { "n": "安吉" }, { "n": "慈溪" }, { "n": "定海" }, { "n": "奉化" }, { "n": "海盐" }, { "n": "黄岩" }, { "n": "湖州" }, { "n": "嘉兴" }, { "n": "金华" }, { "n": "临安" },
{ "n": "临海" }, { "n": "丽水" }, { "n": "宁波" }, { "n": "瓯海" }, { "n": "平湖" }, { "n": "千岛湖" }, { "n": "衢州" }, { "n": "江山" }, { "n": "瑞安" }, { "n": "绍兴" }, { "n": "嵊州" },
{ "n": "台州" }, { "n": "温岭" }, { "n": "温州" }, { "n": "余姚" }, { "n": "舟山" }
]
},
{
"n": "重庆",
"s": [
{ "n": "万州" }, { "n": "涪陵" }, { "n": "渝中" }, { "n": "大渡口" }, { "n": "江北" }, { "n": "沙坪坝" }, { "n": "九龙坡" }, { "n": "南岸" }, { "n": "北碚" }, { "n": "万盛" },
{ "n": "双挢" }, { "n": "渝北" }, { "n": "巴南" }, { "n": "黔江" }, { "n": "长寿" }, { "n": "綦江" }, { "n": "潼南" }, { "n": "铜梁" }, { "n": "大足" }, { "n": "荣昌" }, { "n": "壁山" },
{ "n": "梁平" }, { "n": "城口" }, { "n": "丰都" }, { "n": "垫江" }, { "n": "武隆" }, { "n": "忠县" }, { "n": "开县" }, { "n": "云阳" }, { "n": "奉节" }, { "n": "巫山" }, { "n": "巫溪" },
{ "n": "石柱" }, { "n": "秀山" }, { "n": "酉阳" }, { "n": "彭水" }, { "n": "江津" }, { "n": "合川" }, { "n": "永川" }, { "n": "南川" }
]
},
{
"n": "安徽",
"s": [
{ "n": "合肥" }, { "n": "安庆" }, { "n": "蚌埠" }, { "n": "亳州" }, { "n": "巢湖" }, { "n": "滁州" }, { "n": "阜阳" }, { "n": "贵池" }, { "n": "淮北" }, { "n": "淮化" }, { "n": "淮南" },
{ "n": "黄山" }, { "n": "九华山" }, { "n": "六安" }, { "n": "马鞍山" }, { "n": "宿州" }, { "n": "铜陵" }, { "n": "屯溪" }, { "n": "芜湖" }, { "n": "宣城" }
]
},
{
"n": "福建",
"s": [
{ "n": "福州" }, { "n": "厦门" }, { "n": "泉州" }, { "n": "漳州" }, { "n": "龙岩" }, { "n": "南平" }, { "n": "宁德" }, { "n": "莆田" }, { "n": "三明" }
]
},
{
"n": "甘肃",
"s": [
{ "n": "兰州" }, { "n": "白银" }, { "n": "定西" }, { "n": "敦煌" }, { "n": "甘南" }, { "n": "金昌" }, { "n": "酒泉" }, { "n": "临夏" }, { "n": "平凉" }, { "n": "天水" },
{ "n": "武都" }, { "n": "武威" }, { "n": "西峰" }, { "n": "张掖" }
]
},
{
"n": "广西",
"s": [
{ "n": "南宁" }, { "n": "百色" }, { "n": "北海" }, { "n": "桂林" }, { "n": "防城港" }, { "n": "贵港" }, { "n": "河池" }, { "n": "贺州" }, { "n": "柳州" }, { "n": "钦州" }, { "n": "梧州" }, { "n": "玉林" }
]
},
{
"n": "贵州",
"s": [
{ "n": "贵阳" }, { "n": "安顺" }, { "n": "毕节" }, { "n": "都匀" }, { "n": "凯里" }, { "n": "六盘水" }, { "n": "铜仁" }, { "n": "兴义" }, { "n": "玉屏" }, { "n": "遵义" }
]
},
{
"n": "海南",
"s": [
{ "n": "海口" }, { "n": "儋县" }, { "n": "陵水" }, { "n": "琼海" }, { "n": "三亚" }, { "n": "通什" }, { "n": "万宁" }
]
},
{
"n": "河北",
"s": [
{ "n": "石家庄" }, { "n": "保定" }, { "n": "北戴河" }, { "n": "沧州" }, { "n": "承德" }, { "n": "丰润" }, { "n": "邯郸" }, { "n": "衡水" }, { "n": "廊坊" }, { "n": "南戴河" }, { "n": "秦皇岛" },
{ "n": "唐山" }, { "n": "新城" }, { "n": "邢台" }, { "n": "张家口" }
]
},
{
"n": "黑龙江",
"s": [
{ "n": "哈尔滨" }, { "n": "北安" }, { "n": "大庆" }, { "n": "大兴安岭" }, { "n": "鹤岗" }, { "n": "黑河" }, { "n": "佳木斯" }, { "n": "鸡西" }, { "n": "牡丹江" }, { "n": "齐齐哈尔" },
{ "n": "七台河" }, { "n": "双鸭山" }, { "n": "绥化" }, { "n": "伊春" }
]
},
{
"n": "河南",
"s": [
{ "n": "郑州" }, { "n": "安阳" }, { "n": "鹤壁" }, { "n": "潢川" }, { "n": "焦作" }, { "n": "济源" }, { "n": "开封" }, { "n": "漯河" }, { "n": "洛阳" }, { "n": "南阳" }, { "n": "平顶山" },
{ "n": "濮阳" }, { "n": "三门峡" }, { "n": "商丘" }, { "n": "新乡" }, { "n": "信阳" }, { "n": "许昌" }, { "n": "周口" }, { "n": "驻马店" }
]
},
{
"n": "湖北",
"s": [
{ "n": "武汉" }, { "n": "恩施" }, { "n": "鄂州" }, { "n": "黄冈" }, { "n": "黄石" }, { "n": "荆门" }, { "n": "荆州" }, { "n": "潜江" }, { "n": "十堰" }, { "n": "随州" }, { "n": "武穴" },
{ "n": "仙桃" }, { "n": "咸宁" }, { "n": "襄阳" }, { "n": "襄樊" }, { "n": "孝感" }, { "n": "宜昌" }
]
},
{
"n": "湖南",
"s": [
{ "n": "长沙" }, { "n": "常德" }, { "n": "郴州" }, { "n": "衡阳" }, { "n": "怀化" }, { "n": "吉首" }, { "n": "娄底" }, { "n": "邵阳" }, { "n": "湘潭" }, { "n": "益阳" }, { "n": "岳阳" },
{ "n": "永州" }, { "n": "张家界" }, { "n": "株洲" }
]
},
{
"n": "江西",
"s": [
{ "n": "南昌" }, { "n": "抚州" }, { "n": "赣州" }, { "n": "吉安" }, { "n": "景德镇" }, { "n": "井冈山" }, { "n": "九江" }, { "n": "庐山" }, { "n": "萍乡" },
{ "n": "上饶" }, { "n": "新余" }, { "n": "宜春" }, { "n": "鹰潭" }
]
},
{
"n": "吉林",
"s": [
{ "n": "长春" }, { "n": "吉林" }, { "n": "白城" }, { "n": "白山" }, { "n": "珲春" }, { "n": "辽源" }, { "n": "梅河" }, { "n": "四平" }, { "n": "松原" }, { "n": "通化" }, { "n": "延吉" }
]
},
{
"n": "辽宁",
"s": [
{ "n": "沈阳" }, { "n": "鞍山" }, { "n": "本溪" }, { "n": "朝阳" }, { "n": "大连" }, { "n": "丹东" }, { "n": "抚顺" }, { "n": "阜新" }, { "n": "葫芦岛" }, { "n": "锦州" },
{ "n": "辽阳" }, { "n": "盘锦" }, { "n": "铁岭" }, { "n": "营口" }
]
},
{
"n": "内蒙古",
"s": [
{ "n": "呼和浩特" }, { "n": "阿拉善盟" }, { "n": "包头" }, { "n": "赤峰" }, { "n": "东胜" }, { "n": "海拉尔" }, { "n": "集宁" }, { "n": "临河" }, { "n": "通辽" }, { "n": "乌海" },
{ "n": "乌兰浩特" }, { "n": "锡林浩特" }
]
},
{
"n": "宁夏",
"s": [
{ "n": "银川" }, { "n": "固源" }, { "n": "石嘴山" }, { "n": "吴忠" }
]
},
{
"n": "青海",
"s": [
{ "n": "西宁" }, { "n": "德令哈" }, { "n": "格尔木" }, { "n": "共和" }, { "n": "海东" }, { "n": "海晏" }, { "n": "玛沁" }, { "n": "同仁" }, { "n": "玉树" }
]
},
{
"n": "山东",
"s": [
{ "n": "济南" }, { "n": "滨州" }, { "n": "兖州" }, { "n": "德州" }, { "n": "东营" }, { "n": "菏泽" }, { "n": "济宁" }, { "n": "莱芜" }, { "n": "聊城" }, { "n": "临沂" },
{ "n": "蓬莱" }, { "n": "青岛" }, { "n": "曲阜" }, { "n": "日照" }, { "n": "泰安" }, { "n": "潍坊" }, { "n": "威海" }, { "n": "烟台" }, { "n": "枣庄" }, { "n": "淄博" }
]
},
{
"n": "山西",
"s": [
{ "n": "太原" }, { "n": "长治" }, { "n": "大同" }, { "n": "候马" }, { "n": "晋城" }, { "n": "离石" }, { "n": "临汾" }, { "n": "宁武" }, { "n": "朔州" }, { "n": "忻州" },
{ "n": "阳泉" }, { "n": "榆次" }, { "n": "运城" }
]
},
{
"n": "陕西",
"s": [
{ "n": "西安" }, { "n": "安康" }, { "n": "宝鸡" }, { "n": "汉中" }, { "n": "渭南" }, { "n": "商州" }, { "n": "绥德" }, { "n": "铜川" }, { "n": "咸阳" }, { "n": "延安" }, { "n": "榆林" }
]
},
{
"n": "四川",
"s": [
{ "n": "成都" }, { "n": "巴中" }, { "n": "达川" }, { "n": "德阳" }, { "n": "都江堰" }, { "n": "峨眉山" }, { "n": "涪陵" }, { "n": "广安" }, { "n": "广元" }, { "n": "九寨沟" },
{ "n": "康定" }, { "n": "乐山" }, { "n": "泸州" }, { "n": "马尔康" }, { "n": "绵阳" }, { "n": "眉山" }, { "n": "南充" }, { "n": "内江" }, { "n": "攀枝花" }, { "n": "遂宁" },
{ "n": "汶川" }, { "n": "西昌" }, { "n": "雅安" }, { "n": "宜宾" }, { "n": "自贡" }, { "n": "资阳" }
]
},
{
"n": "天津",
"s": [
{ "n": "天津" }, { "n": "和平" }, { "n": "东丽" }, { "n": "河东" }, { "n": "西青" }, { "n": "河西" }, { "n": "津南" }, { "n": "南开" }, { "n": "北辰" }, { "n": "河北" }, { "n": "武清" }, { "n": "红挢" },
{ "n": "塘沽" }, { "n": "汉沽" }, { "n": "大港" }, { "n": "宁河" }, { "n": "静海" }, { "n": "宝坻" }, { "n": "蓟县" }
]
},
{
"n": "新疆",
"s": [
{ "n": "乌鲁木齐" }, { "n": "阿克苏" }, { "n": "阿勒泰" }, { "n": "阿图什" }, { "n": "博乐" }, { "n": "昌吉" }, { "n": "东山" }, { "n": "哈密" }, { "n": "和田" }, { "n": "喀什" },
{ "n": "克拉玛依" }, { "n": "库车" }, { "n": "库尔勒" }, { "n": "奎屯" }, { "n": "石河子" }, { "n": "塔城" }, { "n": "吐鲁番" }, { "n": "伊宁" }
]
},
{
"n": "西藏",
"s": [
{ "n": "拉萨" }, { "n": "阿里" }, { "n": "昌都" }, { "n": "林芝" }, { "n": "那曲" }, { "n": "日喀则" }, { "n": "山南" }
]
},
{
"n": "云南",
"s": [
{ "n": "昆明" }, { "n": "大理" }, { "n": "保山" }, { "n": "楚雄" }, { "n": "大理" }, { "n": "东川" }, { "n": "个旧" }, { "n": "景洪" }, { "n": "开远" }, { "n": "临沧" }, { "n": "丽江" },
{ "n": "六库" }, { "n": "潞西" }, { "n": "曲靖" }, { "n": "思茅" }, { "n": "文山" }, { "n": "西双版纳" }, { "n": "玉溪" }, { "n": "中甸" }, { "n": "昭通" }
]
},
{
"n": "香港特别行政区",
"s": [
{ "n": "香港" }, { "n": "九龙" }, { "n": "新界" }
]
},
{
"n": "澳门特别行政区",
"s": [
{ "n": { "n": "澳门" } }
]
},
{
"n": "台湾",
"s": [
{ "n": "台北" }, { "n": "基隆" }, { "n": "台南" }, { "n": "台中" }, { "n": "高雄" }, { "n": "屏东" }, { "n": "南投" }, { "n": "云林" }, { "n": "新竹" }, { "n": "彰化" }, { "n": "苗栗" },
{ "n": "嘉义" }, { "n": "花莲" }, { "n": "桃园" }, { "n": "宜兰" }, { "n": "台东" }, { "n": "金门" }, { "n": "马祖" }, { "n": "澎湖" }
]
},
{
"n": "海外",
"s": [
{ "n": "美国" }, { "n": "日本" }, { "n": "英国" }, { "n": "法国" }, { "n": "德国" }, { "n": "其他" }
]
}
]
Loading…
Cancel
Save