# Conflicts: # public/react/src/modules/courses/Index.jstopic_bank
commit
f3e41a5cc2
@ -0,0 +1,14 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
var $modal = $('.modal.admin-message-modal');
|
||||
if ($modal.length > 0) {
|
||||
$modal.on('hide.bs.modal', function(){
|
||||
$modal.find('.modal-body').html('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showMessageModal(html) {
|
||||
var $modal = $('.modal.admin-message-modal');
|
||||
$modal.find('.modal-body').html(html);
|
||||
$modal.modal('show');
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
class Admins::ImportCourseMembersController < Admins::BaseController
|
||||
def create
|
||||
return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile)
|
||||
|
||||
result = Admins::ImportCourseMemberService.call(params[:file].to_io)
|
||||
render_ok(result)
|
||||
rescue Admins::ImportCourseMemberService::Error => ex
|
||||
render_error(ex)
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
class Admins::ImportUsersController < Admins::BaseController
|
||||
def create
|
||||
return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile)
|
||||
|
||||
result = Admins::ImportUserService.call(params[:file].to_io)
|
||||
render_ok(result)
|
||||
rescue Admins::ImportUserService::Error => ex
|
||||
render_error(ex)
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
class Wechats::JsSdkSignaturesController < ApplicationController
|
||||
def create
|
||||
signature = Util::Wechat.js_sdk_signature(params[:url], params[:noncestr], params[:timestamp])
|
||||
render_ok(signature: signature)
|
||||
rescue Util::Wechat::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
class Admins::ImportCourseMemberExcel < BaseImportXlsx
|
||||
Data = Struct.new(:student_id, :name, :course_id, :role, :course_group_name, :school_id)
|
||||
|
||||
def read_each(&block)
|
||||
sheet.each_row_streaming(pad_cells: true, offset: 1) do |row|
|
||||
data = row.map(&method(:cell_value))[0..5]
|
||||
block.call Data.new(*data)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_sheet_valid!
|
||||
raise_import_error('请按照模板格式导入') if sheet.row(1).size != 6
|
||||
end
|
||||
|
||||
def cell_value(obj)
|
||||
obj&.cell_value&.to_s&.strip
|
||||
end
|
||||
end
|
@ -0,0 +1,33 @@
|
||||
class Admins::ImportUserExcel < BaseImportXlsx
|
||||
UserData = Struct.new(:student_id, :name, :department_name, :identity, :technical_title, :phone)
|
||||
|
||||
def read_each(&block)
|
||||
sheet.each_row_streaming(pad_cells: true, offset: 3) do |row|
|
||||
data = row.map(&method(:cell_value))[0..5]
|
||||
block.call UserData.new(*data)
|
||||
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
|
||||
|
||||
def check_sheet_valid!
|
||||
raise_import_error('请按照模板格式导入') if school.blank?
|
||||
end
|
||||
|
||||
def cell_value(obj)
|
||||
obj&.cell_value
|
||||
end
|
||||
end
|
@ -1,5 +1,11 @@
|
||||
class ApplicationImport
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
def logger(msg)
|
||||
Rails.logger.error(msg)
|
||||
end
|
||||
|
||||
def raise_import_error(message)
|
||||
raise Error, message
|
||||
end
|
||||
end
|
@ -0,0 +1,23 @@
|
||||
class BaseImportXlsx < ApplicationImport
|
||||
|
||||
attr_reader :sheet
|
||||
|
||||
def initialize(path)
|
||||
raise Error, '只支持xlsx格式' unless !path.is_a?(String) || path.end_with?('.xlsx')
|
||||
|
||||
begin
|
||||
@sheet = Roo::Excelx.new(path)
|
||||
rescue Exception => ex
|
||||
Util.logger_error(ex)
|
||||
raise Error, '打开文件失败'
|
||||
end
|
||||
|
||||
check_sheet_valid!
|
||||
end
|
||||
|
||||
def read_each(&block);end
|
||||
|
||||
private
|
||||
|
||||
def check_sheet_valid!;end
|
||||
end
|
@ -0,0 +1,74 @@
|
||||
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)
|
||||
str = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }.to_query
|
||||
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,63 @@
|
||||
class Admins::ImportCourseMemberService < ApplicationService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :file, :result
|
||||
|
||||
def initialize(file)
|
||||
@file = file
|
||||
@result = { success: 0, fail: [] }
|
||||
end
|
||||
|
||||
def call
|
||||
raise Error, '文件不存在' if file.blank?
|
||||
|
||||
excel = Admins::ImportCourseMemberExcel.new(file)
|
||||
excel.read_each(&method(:create_course_member))
|
||||
|
||||
result
|
||||
rescue ApplicationImport::Error => ex
|
||||
raise Error, ex.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_course_member(data)
|
||||
raise '课堂角色必须为 2、3、4' unless [2, 3, 4].include?(data.role.to_i)
|
||||
|
||||
user = User.joins(:user_extension).where(user_extensions: { student_id: data.student_id, school_id: data.school_id }).first
|
||||
raise '该学号的用户不存在' if user.blank?
|
||||
course = Course.find_by(id: data.course_id)
|
||||
raise '该课堂不存在' if course.blank?
|
||||
|
||||
course_group = nil
|
||||
if data.course_group_name.present?
|
||||
course_group = course.course_groups.find_or_create_by!(name: data.course_group_name)
|
||||
end
|
||||
|
||||
member = course.course_members.find_by(user_id: user.id, role: data.role.to_i)
|
||||
# 如果已是课堂成员且是学生身份and不在指定的分班则移动到该分班
|
||||
if member.present? && member.role == :STUDENT && course_group && member.course_group_id != course_group&.id
|
||||
member.update!(course_group_id: course_group&.id)
|
||||
elsif member.blank?
|
||||
course.course_members.create!(user_id: user.id, role: data.role.to_i, course_group_id: course_group&.id)
|
||||
extra =
|
||||
case data.role.to_i
|
||||
when 2 then 9
|
||||
when 3 then 7
|
||||
else 10
|
||||
end
|
||||
|
||||
Tiding.create!(user_id: user.id, trigger_user_id: course.tea_id, container_id: course.id,
|
||||
container_type: 'TeacherJoinCourse', belong_container_id: course.id,
|
||||
belong_container_type: 'Course', tiding_type: 'System', extra: extra)
|
||||
end
|
||||
|
||||
result[:success] += 1
|
||||
rescue Exception => ex
|
||||
fail_data = data.as_json
|
||||
fail_data[:data] = fail_data.values.join(',')
|
||||
fail_data[:message] = ex.message
|
||||
|
||||
result[:fail] << fail_data
|
||||
end
|
||||
end
|
@ -0,0 +1,89 @@
|
||||
class Admins::ImportUserService < ApplicationService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :file, :school, :prefix, :result
|
||||
|
||||
def initialize(file)
|
||||
@file = file
|
||||
@result = { success: 0, fail: [] }
|
||||
end
|
||||
|
||||
def call
|
||||
raise Error, '文件不存在' if file.blank?
|
||||
|
||||
excel = Admins::ImportUserExcel.new(file)
|
||||
@school = excel.school
|
||||
@prefix = excel.identifier
|
||||
|
||||
excel.read_each(&method(:save_user))
|
||||
|
||||
result
|
||||
rescue ApplicationImport::Error => ex
|
||||
raise Error, ex.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_user(data)
|
||||
user = find_user(data)
|
||||
|
||||
if user.blank?
|
||||
create_user(data)
|
||||
else
|
||||
user.update_column(:certification, 1)
|
||||
end
|
||||
|
||||
result[:success] += 1
|
||||
rescue Exception => ex
|
||||
fail_data = data.as_json
|
||||
fail_data[:data] = fail_data.values.join(',')
|
||||
fail_data[:message] = ex.message
|
||||
|
||||
result[:fail] << fail_data
|
||||
end
|
||||
|
||||
def create_user(data)
|
||||
department = school.departments.find_by(name: data.department_name)
|
||||
|
||||
attr = {
|
||||
status: User::STATUS_ACTIVE,
|
||||
login: "#{prefix}#{data.student_id}",
|
||||
firstname: '',
|
||||
lastname: data.name,
|
||||
nickname: data.name,
|
||||
professional_certification: 1,
|
||||
certification: 1,
|
||||
password: '12345678',
|
||||
phone: data.phone
|
||||
}
|
||||
ActiveRecord::Base.transaction do
|
||||
user = User.create!(attr)
|
||||
|
||||
extension_attr = {
|
||||
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
|
||||
}
|
||||
|
||||
extension_attr[:technical_title] =
|
||||
case data.identity.to_i
|
||||
when 0 then %w(教授 副教授 讲师 助教).include?(data.technical_title) || '讲师'
|
||||
when 2 then %w(企业管理者 部门管理者 高级工程师 工程师 助理工程师).include?(data.technical_title) || '助理工程师'
|
||||
else nil
|
||||
end
|
||||
|
||||
user.create_user_extension!(extension_attr)
|
||||
end
|
||||
end
|
||||
|
||||
def find_user(data)
|
||||
users = User.joins(:user_extension).where(user_extensions: { identity: data.identity, school_id: school.id })
|
||||
|
||||
if data.identity.to_i == 1
|
||||
users = users.where(user_extensions: { student_id: data.student_id })
|
||||
else
|
||||
users = users.where(user_extensions: { technical_title: data.technical_title }).where('CONCAT(users.lastname,users.firstname) = ?', data.name)
|
||||
end
|
||||
|
||||
users.first
|
||||
end
|
||||
end
|
@ -0,0 +1,18 @@
|
||||
<div class="modal fade admin-message-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" style="max-height: 300px; overflow-y: scroll;">
|
||||
保存成功
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,30 @@
|
||||
<div class="modal fade admin-import-user-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">
|
||||
<form class="admin-import-user-form" enctype="multipart/form-data">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">文件</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input type="file" name="file" class="upload-file-input" id="upload-file-input" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
|
||||
<label class="custom-file-label file-names" for="upload-file-input">选择文件</label>
|
||||
</div>
|
||||
</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,16 @@
|
||||
wechat_config = {}
|
||||
|
||||
begin
|
||||
config = Rails.application.config_for(:configuration)
|
||||
wechat_config = config['wechat']
|
||||
raise 'wechat config missing' if wechat_config.blank?
|
||||
rescue => ex
|
||||
raise ex if Rails.env.production?
|
||||
|
||||
puts %Q{\033[33m [warning] wechat config or configuration.yml missing,
|
||||
please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
|
||||
wechat_config = {}
|
||||
end
|
||||
|
||||
Util::Wechat.appid = wechat_config['appid']
|
||||
Util::Wechat.secret = wechat_config['secret']
|
@ -0,0 +1,12 @@
|
||||
class MigrateExcellentCourseCourseList < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
courses = Course.where(excellent: true)
|
||||
courses.each do |course|
|
||||
if !course.course_list.present? && course.subject
|
||||
subject = course.subject
|
||||
course_list = CourseList.find_by(name: subject.name) || CourseList.create!(name: subject.name, user_id: course.tea_id, is_admin: 0)
|
||||
course.update_attributes(course_list_id: course_list.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in new issue