parent
516c9e7bec
commit
9fccdb7c06
@ -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,2 +1,5 @@
|
|||||||
class PortalImage < ApplicationRecord
|
class PortalImage < ApplicationRecord
|
||||||
|
def online?
|
||||||
|
status?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -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
|
@ -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">×</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>
|
@ -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
|
Loading…
Reference in new issue