Merge branch 'dev_aliyun' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun
commit
2aa630e911
@ -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
|
@ -0,0 +1,5 @@
|
||||
class TemplatesController < ApplicationController
|
||||
def show
|
||||
@template = EcTemplate.find_by_name!(params[:name])
|
||||
end
|
||||
end
|
@ -1,6 +1,6 @@
|
||||
module MemosHelper
|
||||
|
||||
def forum_list
|
||||
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}]
|
||||
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}, {id: 16, name: "通知公告"}]
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,12 @@
|
||||
class CreateDiffRecordJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user_id, obj_id, obj_klass, column_name, before, after)
|
||||
user = User.find_by(id: user_id)
|
||||
obj = obj_klass.constantize.find_by(id: obj_id)
|
||||
|
||||
return if user.blank? || obj.blank?
|
||||
|
||||
CreateDiffRecordService.call(user, obj, column_name, before, after)
|
||||
end
|
||||
end
|
@ -1,75 +0,0 @@
|
||||
module Util::Wechat
|
||||
BASE_SITE = 'https://api.weixin.qq.com'.freeze
|
||||
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
def js_sdk_signature(url, noncestr, timestamp)
|
||||
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
|
||||
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
|
||||
Digest::SHA1.hexdigest(str)
|
||||
end
|
||||
|
||||
def access_token
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
|
||||
result['access_token']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_access_token
|
||||
Rails.cache.delete(access_token_cache_key)
|
||||
access_token
|
||||
end
|
||||
|
||||
def jsapi_ticket
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
|
||||
result['ticket']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_jsapi_ticket
|
||||
Rails.cache.delete(jsapi_ticket_cache_key)
|
||||
jsapi_ticket
|
||||
end
|
||||
|
||||
def access_token_cache_key
|
||||
"#{base_cache_key}/access_token"
|
||||
end
|
||||
|
||||
def jsapi_ticket_cache_key
|
||||
"#{base_cache_key}/jsapi_ticket"
|
||||
end
|
||||
|
||||
def base_cache_key
|
||||
"wechat/#{appid}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, url, **params)
|
||||
Rails.logger.error("[wechat] request: #{method} #{url} #{params.inspect}")
|
||||
|
||||
client = Faraday.new(url: BASE_SITE)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}")
|
||||
|
||||
if response.status != 200
|
||||
raise Error, result.inspect
|
||||
end
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_i.nonzero?
|
||||
raise Error, result.inspect
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,2 @@
|
||||
module Wechat
|
||||
end
|
@ -0,0 +1,11 @@
|
||||
class Wechat::App
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jscode2session, to: :client
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,74 @@
|
||||
class Wechat::Client
|
||||
BASE_SITE = 'https://api.weixin.qq.com'.freeze
|
||||
|
||||
attr_reader :appid, :secret
|
||||
|
||||
def initialize(appid, secret)
|
||||
@appid = appid
|
||||
@secret = secret
|
||||
end
|
||||
|
||||
def access_token
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
|
||||
result['access_token']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_access_token
|
||||
Rails.cache.delete(access_token_cache_key)
|
||||
access_token
|
||||
end
|
||||
|
||||
def jsapi_ticket
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
|
||||
result['ticket']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_jsapi_ticket
|
||||
Rails.cache.delete(jsapi_ticket_cache_key)
|
||||
jsapi_ticket
|
||||
end
|
||||
|
||||
def jscode2session(code)
|
||||
request(:get, '/sns/jscode2session', appid: appid, secret: secret, js_code: code, grant_type: 'authorization_code')
|
||||
end
|
||||
|
||||
def access_token_cache_key
|
||||
"#{base_cache_key}/access_token"
|
||||
end
|
||||
|
||||
def jsapi_ticket_cache_key
|
||||
"#{base_cache_key}/jsapi_ticket"
|
||||
end
|
||||
|
||||
def base_cache_key
|
||||
"wechat/#{appid}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, url, **params)
|
||||
Rails.logger.error("[wechat] request: #{method} #{url} #{params.except(:secret).inspect}")
|
||||
|
||||
client = Faraday.new(url: BASE_SITE)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}")
|
||||
|
||||
if response.status != 200
|
||||
raise Wechat::Error.parse(result)
|
||||
end
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_i.nonzero?
|
||||
raise Wechat::Error.parse(result)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
class Wechat::Error < StandardError
|
||||
attr_reader :code
|
||||
|
||||
def initialize(code, message)
|
||||
super message
|
||||
@code = code
|
||||
end
|
||||
|
||||
class << self
|
||||
def parse(result)
|
||||
new(result['errcode'], result['errmsg'])
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
class Wechat::OfficialAccount
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jsapi_ticket, to: :client
|
||||
|
||||
def js_sdk_signature(url, noncestr, timestamp)
|
||||
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
|
||||
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
|
||||
Digest::SHA1.hexdigest(str)
|
||||
end
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
class DiffRecord < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :container, polymorphic: true
|
||||
|
||||
has_one :diff_record_content, dependent: :destroy
|
||||
|
||||
delegate :content, to: :diff_record_content
|
||||
end
|
@ -0,0 +1,3 @@
|
||||
class DiffRecordContent < ApplicationRecord
|
||||
belongs_to :diff_record
|
||||
end
|
@ -1,2 +1,3 @@
|
||||
class Forum < ApplicationRecord
|
||||
has_many :memos, dependent: :destroy
|
||||
end
|
||||
|
@ -1,2 +1,5 @@
|
||||
class PortalImage < ApplicationRecord
|
||||
def online?
|
||||
status?
|
||||
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? || move.id == after.id # 移动至末尾
|
||||
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 - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,46 @@
|
||||
class CreateDiffRecordService < ApplicationService
|
||||
attr_reader :user, :obj, :column_name, :after, :before
|
||||
|
||||
def initialize(user, obj, column_name, before, after)
|
||||
@user = user
|
||||
@obj = obj
|
||||
@before = before
|
||||
@after = after
|
||||
@column_name = column_name
|
||||
end
|
||||
|
||||
def call
|
||||
ActiveRecord::Base.transaction do
|
||||
diff_record = DiffRecord.create!(user: user, container: obj, column_name: column_name)
|
||||
diff_record.create_diff_record_content!(content: diff_content)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diff_content
|
||||
content = ''
|
||||
|
||||
arr = []
|
||||
index = 0
|
||||
fragment_size = 1
|
||||
Diffy::Diff.new(before, after).each do |line|
|
||||
unless line =~ /^[\+-]/
|
||||
if arr.empty? && index < fragment_size
|
||||
content += line
|
||||
index += 1
|
||||
else
|
||||
index = 0
|
||||
arr << line
|
||||
arr.shift if arr.size > fragment_size
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
content += arr.join('') if arr.present?
|
||||
content += line
|
||||
arr.clear
|
||||
end
|
||||
content
|
||||
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>
|
@ -1 +1,7 @@
|
||||
json.course_evaluations @course_evaluations, partial: 'ecs/course_evaluations/shared/ec_course_evaluation_slim', as: :ec_course_evaluation
|
||||
json.course_evaluations do
|
||||
json.array! @course_evaluations do |course_evaluation|
|
||||
json.partial! 'ecs/course_evaluations/shared/ec_course_evaluation_slim', ec_course_evaluation: course_evaluation
|
||||
|
||||
json.evaluation_relates course_evaluation.evaluation_relates
|
||||
end
|
||||
end
|
@ -1 +0,0 @@
|
||||
json.partial! 'ecs/shared/user', user: @user
|
@ -1,3 +1,4 @@
|
||||
json.status 1
|
||||
json.message "发送成功"
|
||||
json.course_id @course.id
|
||||
json.course_id @course.id
|
||||
json.first_category_url module_url(@course.none_hidden_course_modules.first, @course)
|
@ -0,0 +1,3 @@
|
||||
json.template do
|
||||
json.partial! 'attachments/attachment_simple', attachment: @template.attachments.last
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
class MigrateForumName < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
forum = Forum.find_by(id: 16)
|
||||
if forum.present?
|
||||
forum.update_attributes(name: "通知公告")
|
||||
forum.memos.destroy_all
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,11 @@
|
||||
class CreateDiffRecords < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :diff_records do |t|
|
||||
t.references :user
|
||||
t.references :container, polymorphic: true
|
||||
t.string :column_name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
class CreateDiffRecordContents < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :diff_record_contents do |t|
|
||||
t.references :diff_record
|
||||
|
||||
t.text :content
|
||||
end
|
||||
end
|
||||
end
|
@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,24 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Link} from 'react-router-dom'
|
||||
|
||||
const map={"blue":"colorblue","white":"colorwhite","grey":"colorgrey", 'orange': "color-orange"}
|
||||
class WordsBtn extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let{to, href}=this.props
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
to==undefined ?
|
||||
<a href={href || "javascript:void(0)"} onClick={this.props.onClick} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</a>
|
||||
:
|
||||
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</Link>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
import React, { Component } from 'react';
|
||||
import {Link} from 'react-router-dom'
|
||||
|
||||
const map={"blue":"colorblue","white":"colorwhite","grey":"colorgrey", 'orange': "color-orange"}
|
||||
class WordsBtn extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let{to, href,targets}=this.props
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
to==undefined&&targets==undefined ?
|
||||
<a href={href || "javascript:void(0)"} onClick={this.props.onClick} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</a>:
|
||||
targets!=undefined? <a href={to} target="_blank" className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</a>
|
||||
:
|
||||
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</Link>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default WordsBtn;
|
@ -1,184 +1,184 @@
|
||||
import React, { Component } from 'react';
|
||||
export const STATUS_UN_PUBLISH = "未发布"
|
||||
// homework_status: ["提交中", "未开启补交"]
|
||||
export const STATUS_SUBMIT = "提交中"
|
||||
// export const STATUS_UN_PUBLISH = "未发布"
|
||||
|
||||
// homework_status: ["提交中"] 未发布 未开启补交
|
||||
|
||||
|
||||
export function RouteHOC(options = {}) {
|
||||
return function wrap(WrappedComponent) {
|
||||
return class Wrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
toCreateProject = () => {
|
||||
let url = '/projects/new'
|
||||
if (window.location.port == 3007) {
|
||||
// window.location.href
|
||||
url = '/testbdweb.educoder.net/projects/new'
|
||||
}
|
||||
window.open(
|
||||
url,
|
||||
'_blank' // <- This is what makes it open in a new window.
|
||||
);
|
||||
}
|
||||
// common_homework group_homework
|
||||
// 是否是分组作业
|
||||
isGroup = () => {
|
||||
return window.location.pathname.indexOf('group_homeworks') != -1
|
||||
}
|
||||
getModuleName = (isChinese) => {
|
||||
const isGroup = this.isGroup()
|
||||
if (isChinese) {
|
||||
let chName = isGroup ? '分组作业' : '普通作业'
|
||||
return chName;
|
||||
}
|
||||
const secondName = isGroup ? 'group_homeworks' : 'common_homeworks'
|
||||
return secondName;
|
||||
}
|
||||
getModuleType = () => {
|
||||
const isGroup = this.isGroup()
|
||||
return isGroup ? 3 : 1
|
||||
}
|
||||
toDetailPage = (_courseId, workId, topicId) => {
|
||||
if (typeof _courseId == "object") {
|
||||
const topicId = _courseId.topicId
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/boards/${workId}/messages/${topicId}`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/boards/${workId}/messages/${topicId}`)
|
||||
}
|
||||
|
||||
}
|
||||
toEditPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/edit`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/edit`)
|
||||
}
|
||||
}
|
||||
toWorkDetailPage = (_courseId, _workId, _studentWorkId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
const studentWorkId = _courseId.studentWorkId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/${_studentWorkId || studentWorkId}/appraise`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/${_studentWorkId}/appraise`)
|
||||
}
|
||||
}
|
||||
toNewPage = (courseId) => {
|
||||
const secondName = this.getModuleName()
|
||||
this.props.history.push(`/courses/${courseId.coursesId}/${secondName}/${courseId.category_id}/new`)
|
||||
}
|
||||
toListPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}${_workId ? '/' + _workId : ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toWorkPostPage = (_courseId, _workId, isEdit, _studentWorkId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
const studentWorkId = _courseId.studentWorkId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/${isEdit? `${_studentWorkId || studentWorkId}/post_edit` : 'post'}`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/${isEdit? `${_studentWorkId}/post_edit` : 'post'}`)
|
||||
}
|
||||
}
|
||||
toWorkListPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/list`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/list`)
|
||||
}
|
||||
}
|
||||
toWorkAnswerPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/answer`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/answer`)
|
||||
}
|
||||
}
|
||||
|
||||
toWorkQuestionPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _workId || _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/question`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/question`)
|
||||
}
|
||||
}
|
||||
|
||||
toWorkSettingPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/setting`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/setting`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
const { snackbarOpen} = this.state;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<WrappedComponent {...this.props}
|
||||
toDetailPage={this.toDetailPage}
|
||||
toEditPage={this.toEditPage}
|
||||
toNewPage={this.toNewPage}
|
||||
toListPage={this.toListPage}
|
||||
toWorkDetailPage={this.toWorkDetailPage}
|
||||
|
||||
toWorkPostPage={this.toWorkPostPage}
|
||||
toWorkListPage={this.toWorkListPage}
|
||||
toWorkAnswerPage={this.toWorkAnswerPage}
|
||||
toWorkQuestionPage={this.toWorkQuestionPage}
|
||||
toWorkSettingPage={this.toWorkSettingPage}
|
||||
|
||||
toCreateProject={this.toCreateProject}
|
||||
|
||||
isGroup={this.isGroup}
|
||||
getModuleName={this.getModuleName}
|
||||
getModuleType={this.getModuleType}
|
||||
|
||||
>
|
||||
|
||||
</WrappedComponent>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
import React, { Component } from 'react';
|
||||
export const STATUS_UN_PUBLISH = "未发布"
|
||||
// homework_status: ["提交中", "未开启补交"]
|
||||
export const STATUS_SUBMIT = "提交中"
|
||||
// export const STATUS_UN_PUBLISH = "未发布"
|
||||
|
||||
// homework_status: ["提交中"] 未发布 未开启补交
|
||||
|
||||
|
||||
export function RouteHOC(options = {}) {
|
||||
return function wrap(WrappedComponent) {
|
||||
return class Wrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
toCreateProject = () => {
|
||||
let url = '/projects/new'
|
||||
if (window.location.port == 3007) {
|
||||
// window.location.href
|
||||
url = '/testbdweb.educoder.net/projects/new'
|
||||
}
|
||||
window.open(
|
||||
url,
|
||||
'_blank' // <- This is what makes it open in a new window.
|
||||
);
|
||||
}
|
||||
// common_homework group_homework
|
||||
// 是否是分组作业
|
||||
isGroup = () => {
|
||||
return window.location.pathname.indexOf('group_homeworks') != -1
|
||||
}
|
||||
getModuleName = (isChinese) => {
|
||||
const isGroup = this.isGroup()
|
||||
if (isChinese) {
|
||||
let chName = isGroup ? '分组作业' : '普通作业'
|
||||
return chName;
|
||||
}
|
||||
const secondName = isGroup ? 'group_homeworks' : 'common_homeworks'
|
||||
return secondName;
|
||||
}
|
||||
getModuleType = () => {
|
||||
const isGroup = this.isGroup()
|
||||
return isGroup ? 3 : 1
|
||||
}
|
||||
toDetailPage = (_courseId, workId, topicId) => {
|
||||
if (typeof _courseId == "object") {
|
||||
const topicId = _courseId.topicId
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/boards/${workId}/messages/${topicId}`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/boards/${workId}/messages/${topicId}`)
|
||||
}
|
||||
|
||||
}
|
||||
toEditPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/edit`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/edit`)
|
||||
}
|
||||
}
|
||||
toWorkDetailPage = (_courseId, _workId, _studentWorkId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
const studentWorkId = _courseId.studentWorkId
|
||||
window.open(`/courses/${courseId}/${secondName}/${_workId || workId}/${_studentWorkId || studentWorkId}/appraise`);
|
||||
} else {
|
||||
window.open(`/courses/${_courseId}/${secondName}/${_workId}/${_studentWorkId}/appraise`);
|
||||
}
|
||||
}
|
||||
toNewPage = (courseId) => {
|
||||
const secondName = this.getModuleName()
|
||||
this.props.history.push(`/courses/${courseId.coursesId}/${secondName}/${courseId.category_id}/new`)
|
||||
}
|
||||
toListPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}${_workId ? '/' + _workId : ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toWorkPostPage = (_courseId, _workId, isEdit, _studentWorkId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
const studentWorkId = _courseId.studentWorkId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/${isEdit? `${_studentWorkId || studentWorkId}/post_edit` : 'post'}`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/${isEdit? `${_studentWorkId}/post_edit` : 'post'}`)
|
||||
}
|
||||
}
|
||||
toWorkListPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/list`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/list`)
|
||||
}
|
||||
}
|
||||
toWorkAnswerPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/answer`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/answer`)
|
||||
}
|
||||
}
|
||||
|
||||
toWorkQuestionPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _workId || _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/question`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/question`)
|
||||
}
|
||||
}
|
||||
|
||||
toWorkSettingPage = (_courseId, _workId) => {
|
||||
const secondName = this.getModuleName()
|
||||
if (typeof _courseId == "object") {
|
||||
const workId = _courseId.workId
|
||||
const courseId = _courseId.coursesId
|
||||
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/setting`)
|
||||
} else {
|
||||
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/setting`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
const { snackbarOpen} = this.state;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<WrappedComponent {...this.props}
|
||||
toDetailPage={this.toDetailPage}
|
||||
toEditPage={this.toEditPage}
|
||||
toNewPage={this.toNewPage}
|
||||
toListPage={this.toListPage}
|
||||
toWorkDetailPage={this.toWorkDetailPage}
|
||||
|
||||
toWorkPostPage={this.toWorkPostPage}
|
||||
toWorkListPage={this.toWorkListPage}
|
||||
toWorkAnswerPage={this.toWorkAnswerPage}
|
||||
toWorkQuestionPage={this.toWorkQuestionPage}
|
||||
toWorkSettingPage={this.toWorkSettingPage}
|
||||
|
||||
toCreateProject={this.toCreateProject}
|
||||
|
||||
isGroup={this.isGroup}
|
||||
getModuleName={this.getModuleName}
|
||||
getModuleType={this.getModuleType}
|
||||
|
||||
>
|
||||
|
||||
</WrappedComponent>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue