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

dev_local_2
daiao 5 years ago
commit e9f353bbd8

@ -0,0 +1,125 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-carousels-index-page').length > 0) {
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
var name = $('.custom-carousel-item-' + id).find('.name-input').val();
if(!name || name.length == 0){
$.notify({ message: '名称不能为空' },{ type: 'danger' });
return;
}
$link.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link, name: name },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.carousels-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { status: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.custom-carousel-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('carousels-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.admin-add-carousel-modal');
var $createForm = $createModal.find('form.admin-add-carousel-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"portal_image[image]": {
required: true
},
"portal_image[name]": {
required: true
},
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$createModal.on('click', '.submit-btn', function() {
$createForm.find('.error').html('');
if ($createForm.valid()) {
$createForm.submit();
} else {
$createForm.find('.error').html('请选择图片');
}
});
$createModal.on('change', '.img-file-input', function(){
var file = $(this)[0].files[0];
$createModal.find('.file-names').html(file ? file.name : '请选择文件');
})
// -------------- 重新上传图片 --------------
//replace_image_url
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $carouselItem = $('.custom-carousel-item-' + data.source_id);
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
})
}
})

@ -0,0 +1,60 @@
.admins-carousels-index-page {
.carousels-card {
.custom-carousel-item {
& > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
&-no {
font-size: 28px;
text-align: center;
}
&-img {
cursor: pointer;
width: 100%;
height: 60px;
& > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
}
.not_active {
background: #F0F0F0;
}
.delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
.save-url-btn {
cursor: pointer;
}
.operate-box {
display: flex;
justify-content: space-between;
align-items: center;
}
.online-check-box {
font-size: 20px;
}
.name-input {
flex: 1;
}
.link-input {
flex: 3;
}
}
}
}

@ -0,0 +1,80 @@
class Admins::CarouselsController < Admins::BaseController
before_action :convert_file!, only: [:create]
def index
@images = PortalImage.order(position: :asc)
end
def create
position = PortalImage.count + 1
ActiveRecord::Base.transaction do
image = PortalImage.create!(create_params.merge(position: position))
file_path = Util::FileManage.disk_filename('PortalImage', image.id)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(@file, file_path)
end
flash[:success] = '保存成功'
redirect_to admins_carousels_path
end
def update
current_image.update!(update_params)
render_ok
end
def destroy
ActiveRecord::Base.transaction do
current_image.destroy!
# 前移
PortalImage.where('position > ?', current_image.position)
.update_all('position = position - 1')
file_path = Util::FileManage.disk_filename('PortalImage', current_image.id)
File.delete(file_path) if File.exist?(file_path)
end
render_delete_success
end
def drag
move = PortalImage.find_by(id: params[:move_id])
after = PortalImage.find_by(id: params[:after_id])
Admins::DragPortalImageService.call(move, after)
render_ok
rescue Admins::DragPortalImageService::Error => e
render_error(e.message)
end
private
def current_image
@_current_image ||= PortalImage.find(params[:id])
end
def create_params
params.require(:portal_image).permit(:name, :link)
end
def update_params
params.permit(:name, :link, :status)
end
def convert_file!
max_size = 10 * 1024 * 1024 # 10M
file = params.dig('portal_image', 'image')
if file.class == ActionDispatch::Http::UploadedFile
@file = file
render_error('请上传文件') if @file.size.zero?
render_error('文件大小超过限制') if @file.size > max_size
else
file = file.to_s.strip
return render_error('请上传正确的图片') if file.blank?
@file = Util.convert_base64_image(file, max_size: max_size)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
end

@ -1,33 +1,19 @@
class Admins::ImportUserExcel < BaseImportXlsx class Admins::ImportUserExcel < BaseImportXlsx
UserData = Struct.new(:student_id, :name, :department_name, :identity, :technical_title, :phone) UserData = Struct.new(:name, :phone, :mail, :school, :department, :identity, :student_id)
def read_each(&block) def read_each(&block)
sheet.each_row_streaming(pad_cells: true, offset: 3) do |row| sheet.each_row_streaming(pad_cells: true, offset: 1) do |row|
data = row.map(&method(:cell_value))[0..5] data = row.map(&method(:cell_value))[0..7]
block.call UserData.new(*data) block.call UserData.new(*data)
end end
end end
def school
@school ||= begin
school_id = sheet.cell(1, 1).to_s.strip
school_name = sheet.cell(1, 2).to_s.strip
School.find_by(id: school_id, name: school_name)
end
end
def identifier
@_identifier ||= sheet.cell(2, 1).to_s.strip
end
private private
def check_sheet_valid! def check_sheet_valid!
raise_import_error('请按照模板格式导入') if school.blank?
end end
def cell_value(obj) def cell_value(obj)
obj&.cell_value obj&.cell_value&.presence
end end
end end

@ -1,2 +1,5 @@
class PortalImage < ApplicationRecord class PortalImage < ApplicationRecord
def online?
status?
end
end end

@ -28,6 +28,8 @@ class User < ApplicationRecord
MIX_PASSWORD_LIMIT = 8 MIX_PASSWORD_LIMIT = 8
CHARS = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
has_one :user_extension, dependent: :destroy has_one :user_extension, dependent: :destroy
accepts_nested_attributes_for :user_extension, update_only: true accepts_nested_attributes_for :user_extension, update_only: true
@ -611,6 +613,15 @@ class User < ApplicationRecord
admin? || business? admin? || business?
end end
def self.generate_login(prefix = 'p')
code = CHARS.sample(8).join
while User.exists?(login: prefix + code) do
code = CHARS.sample(8).join
end
prefix + code
end
protected protected
def validate_password_length def validate_password_length
# 管理员的初始密码是5位 # 管理员的初始密码是5位

@ -0,0 +1,35 @@
class Admins::DragPortalImageService < ApplicationService
Error = Class.new(StandardError)
attr_reader :move, :after
def initialize(move, after)
@move = move
@after = after # 移动后下一个位置的元素
end
def call
return if move.position + 1 == after&.position # 未移动
images = PortalImage.all
ActiveRecord::Base.transaction do
if after.blank? # 移动至末尾
total = images.count
images.where('position > ?', move.position).update_all('position = position - 1')
move.update!(position: total)
return
end
if move.position > after.position # 前移
images.where('position >= ? AND position < ?', after.position, move.position).update_all('position = position + 1')
move.update!(position: after.position)
else # 后移
images.where('position > ? AND position <= ?', move.position, after.position).update_all('position = position - 1')
move.update!(position: after.position)
end
end
end
end

@ -1,7 +1,8 @@
class Admins::ImportUserService < ApplicationService class Admins::ImportUserService < ApplicationService
Error = Class.new(StandardError) Error = Class.new(StandardError)
attr_reader :file, :school, :prefix, :result attr_reader :file, :result
attr_accessor :school, :department
def initialize(file) def initialize(file)
@file = file @file = file
@ -12,9 +13,6 @@ class Admins::ImportUserService < ApplicationService
raise Error, '文件不存在' if file.blank? raise Error, '文件不存在' if file.blank?
excel = Admins::ImportUserExcel.new(file) excel = Admins::ImportUserExcel.new(file)
@school = excel.school
@prefix = excel.identifier
excel.read_each(&method(:save_user)) excel.read_each(&method(:save_user))
result result
@ -25,68 +23,63 @@ class Admins::ImportUserService < ApplicationService
private private
def save_user(data) def save_user(data)
user = find_user(data) ActiveRecord::Base.transaction do
if school.blank? || school.name != data.school
if user.blank? @school = School.find_or_create_by!(name: data.school)
create_user(data) end
else if department.blank? || department.school_id != school.id || department.name != data.department
user.update_column(:certification, 1) @department = school.departments.find_or_initialize_by(name: data.department)
@department.is_auth = true
@department.save!
end end
result[:success] += 1 user =
rescue Exception => ex if data.phone && data.mail
fail_data = data.as_json User.find_by(phone: data.phone, mail: data.mail)
fail_data[:data] = fail_data.values.join(',') elsif data.phone && data.mail.blank?
fail_data[:message] = ex.message User.find_by(phone: data.phone)
elsif data.phone.blank? && data.mail
result[:fail] << fail_data User.find_by(mail: data.mail)
elsif
User.joins(:user_extension).where(user_extensions: { student_id: data.student_id }).first
end end
def create_user(data) user ||= User.new
department = school.departments.find_by(name: data.department_name)
attr = { attrs = {
type: 'User', type: 'User',
status: User::STATUS_ACTIVE, status: User::STATUS_ACTIVE,
login: "#{prefix}#{data.student_id}", login: user.login.presence || data.student_id || User.generate_login,
firstname: '', firstname: '',
lastname: data.name, lastname: data.name,
nickname: data.name, nickname: data.name,
password: '12345678',
professional_certification: 1, professional_certification: 1,
certification: 1, certification: 1,
password: '12345678',
phone: data.phone, phone: data.phone,
mail: "#{prefix}#{data.student_id}@qq.com", mail: data.mail,
profile_completed: true profile_completed: true,
} }
ActiveRecord::Base.transaction do user.assign_attributes(attrs)
user = User.create!(attr) user.save!
extension_attr = { identity = data.identity.present? ? data.identity.to_i : 2
extension_attrs = {
school_id: school.id, location: school.province, location_city: school.city, school_id: school.id, location: school.province, location_city: school.city,
gender: 0, identity: data.identity.to_i, department_id: department&.id, student_id: data.student_id gender: 0, identity: identity, department_id: department.id, student_id: data.student_id.presence
} }
extension = user.user_extension || user.build_user_extension
extension_attr[:technical_title] = extension.assign_attributes(extension_attrs)
case data.identity.to_i extension.save!
when 0 then %w(教授 副教授 讲师 助教).include?(data.technical_title) ? data.technical_title : '讲师'
when 2 then %w(企业管理者 部门管理者 高级工程师 工程师 助理工程师).include?(data.technical_title) ? data.technical_title : '助理工程师'
else nil
end
user.create_user_extension!(extension_attr)
end
end end
def find_user(data) result[:success] += 1
users = User.joins(:user_extension).where(user_extensions: { identity: data.identity, school_id: school.id }) rescue Exception => ex
fail_data = data.as_json
if data.identity.to_i == 1 fail_data[:data] = fail_data.values.join('')
users = users.where(user_extensions: { student_id: data.student_id }) fail_data[:message] = ex.message
else
users = users.where(user_extensions: { technical_title: data.technical_title }).where('CONCAT(users.lastname,users.firstname) = ?', data.name)
end
users.first result[:fail] << fail_data
end end
end end

@ -0,0 +1,43 @@
<%
define_admin_breadcrumbs do
add_admin_breadcrumb('轮播图')
end
%>
<div class="card mb-5 carousels-card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="flex-1">首页轮播图<span class="text-secondary font-12">(拖动排序)</span></span>
<%= javascript_void_link '添加', class: 'btn btn-primary btn-sm add-btn', data: { toggle: 'modal', target: '.admin-add-carousel-modal' } %>
</div>
<div class="card-body row" id="carousels-container">
<% @images.each_with_index do |image, index| %>
<div class="col-12 custom-carousel-item custom-carousel-item-<%= image.id %>" data-id="<%= image.id %>">
<div class="border rounded relative p-3 mb-3 drag row align-items-center <%= image.online? ? '' : 'not_active' %>">
<div class="col-2 col-md-1 custom-carousel-item-no"><%= index + 1 %></div>
<div class="col-10 col-md-3 custom-carousel-item-img" data-source-id="<%= image.id %>" data-source-type="PortalImage" data-toggle="modal" data-target=".admin-upload-file-modal">
<img src="<%= Util::FileManage.exist?('PortalImage', image.id) ? Util::FileManage.disk_file_url('PortalImage', image.id) : '' %>" data-toggle="tooltip" data-title="重新上传"/>
</div>
<div class="col-10 col-md-7">
<div class="input-group">
<input type="text" value="<%= image.name %>" class="form-control name-input" placeholder="请输入名称" />
<input type="text" value="<%= image.link %>" class="form-control link-input" placeholder="请输入跳转地址">
<div class="input-group-prepend">
<button class="input-group-text save-data-btn" data-id="<%= image.id %>">保存</button>
</div>
</div>
</div>
<div class="col-2 col-md-1 operate-box">
<%= check_box_tag(:online, 1, image.online?, id: nil, class: 'online-check-box', data: { id: image.id, toggle: 'tooltip', title: '首页展示' }) %>
<%= delete_link '删除', admins_carousel_path(image, element: ".custom-carousel-item-#{image.id}", not_refresh: true), class: 'delete-btn' do %>
<i class="fa fa-trash-o" data-id="<%= image.id %>"></i>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<%= render partial: 'admins/carousels/shared/add_carousel_modal' %>
<%= render partial: 'admins/shared/modal/upload_file_modal' %>

@ -0,0 +1,36 @@
<div class="modal fade admin-add-carousel-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">添加轮播图</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= simple_form_for(PortalImage.new, url: admins_carousels_path, html: { class: 'admin-add-carousel-form', enctype: 'multipart/form-data' }) do |f| %>
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">图片</span>
</div>
<div class="custom-file flex-row-reverse">
<input type="file" name="portal_image[image]" class="img-file-input" id="img-file-input" accept="image/*">
<label class="custom-file-label file-names" for="img-file-input">选择文件</label>
</div>
</div>
</div>
<%= f.input :name, label: '名称', placeholder: '请输入名称' %>
<%= f.input :link, as: :url, label: '跳转地址', placeholder: '请输入跳转地址' %>
<div class="error text-danger"></div>
<% end %>
</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>

@ -77,7 +77,8 @@
</li> </li>
<li> <li>
<%= sidebar_item_group('#helps-submenu', '帮助中心', icon: 'info-circle') do %> <%= sidebar_item_group('#setting-submenu', '网站建设', icon: 'cogs') do %>
<li><%= sidebar_item(admins_carousels_path, '轮播图', icon: 'image', controller: 'admins-carousels') %></li>
<li><%= sidebar_item(edit_admins_about_path, '关于我们', icon: 'smile-o', controller: 'admins-abouts') %></li> <li><%= sidebar_item(edit_admins_about_path, '关于我们', icon: 'smile-o', controller: 'admins-abouts') %></li>
<li><%= sidebar_item(edit_admins_contact_us_path, '联系我们', icon: 'commenting-o', controller: 'admins-contact_us') %></li> <li><%= sidebar_item(edit_admins_contact_us_path, '联系我们', icon: 'commenting-o', controller: 'admins-contact_us') %></li>
<li><%= sidebar_item(admins_cooperatives_path, '合作伙伴', icon: 'handshake-o', controller: 'admins-cooperatives') %></li> <li><%= sidebar_item(admins_cooperatives_path, '合作伙伴', icon: 'handshake-o', controller: 'admins-cooperatives') %></li>

@ -967,6 +967,9 @@ Rails.application.routes.draw do
post :drag, on: :collection post :drag, on: :collection
post :replace_image_url, on: :member post :replace_image_url, on: :member
end end
resources :carousels, only: [:index, :create, :update, :destroy] do
post :drag, on: :collection
end
end end
resources :colleges, only: [] do resources :colleges, only: [] do

@ -0,0 +1,7 @@
class ResortPortalImageData < ActiveRecord::Migration[5.2]
def change
PortalImage.order(position: :asc).each_with_index do |image, index|
image.update!(position: index + 1)
end
end
end

File diff suppressed because one or more lines are too long

@ -133846,6 +133846,132 @@ $(document).on('turbolinks:load', function(){
$('.batch-all-check-box').prop('checked', allChecked); $('.batch-all-check-box').prop('checked', allChecked);
}) })
}); });
$(document).on('turbolinks:load', function() {
if ($('body.admins-carousels-index-page').length > 0) {
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
var name = $('.custom-carousel-item-' + id).find('.name-input').val();
if(!name || name.length == 0){
$.notify({ message: '名称不能为空' },{ type: 'danger' });
return;
}
$link.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link, name: name },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.carousels-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { status: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.custom-carousel-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('carousels-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.admin-add-carousel-modal');
var $createForm = $createModal.find('form.admin-add-carousel-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"portal_image[image]": {
required: true
},
"portal_image[name]": {
required: true
},
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$createModal.on('click', '.submit-btn', function() {
$createForm.find('.error').html('');
if ($createForm.valid()) {
$createForm.submit();
} else {
$createForm.find('.error').html('请选择图片');
}
});
$createModal.on('change', '.img-file-input', function(){
var file = $(this)[0].files[0];
$createModal.find('.file-names').html(file ? file.name : '请选择文件');
})
// -------------- 重新上传图片 --------------
//replace_image_url
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $carouselItem = $('.custom-carousel-item-' + data.source_id);
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
})
}
})
;
$(document).on('turbolinks:load', function() { $(document).on('turbolinks:load', function() {
var $refuseModal = $('.admin-common-refuse-modal'); var $refuseModal = $('.admin-common-refuse-modal');
if ($refuseModal.length > 0) { if ($refuseModal.length > 0) {
Loading…
Cancel
Save