Merge branch 'dev_aliyun' of http://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun
commit
4c6126273c
@ -0,0 +1,73 @@
|
|||||||
|
$(document).on('turbolinks:load', function() {
|
||||||
|
if ($('body.admins-user-statistics-index-page').length > 0) {
|
||||||
|
var $form = $('.user-statistic-list-form');
|
||||||
|
|
||||||
|
// ************** 学校选择 *************
|
||||||
|
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) {
|
||||||
|
$form.find('.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) {
|
||||||
|
$form.find('#school_id').val(item.id);
|
||||||
|
}
|
||||||
|
return item.name || item.text;
|
||||||
|
},
|
||||||
|
matcher: matcherFunc
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 初始化学校选择器
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/schools/for_option.json',
|
||||||
|
dataType: 'json',
|
||||||
|
type: 'GET',
|
||||||
|
success: function(data) {
|
||||||
|
defineSchoolSelect(data.schools);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空
|
||||||
|
$form.on('click', '.clear-btn', function(){
|
||||||
|
$form.find('select[name="date"]').val('');
|
||||||
|
$form.find('.school-select').val('').trigger('change');
|
||||||
|
$form.find('input[type="submit"]').trigger('click');
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
$('.export-action').on('click', function(){
|
||||||
|
var form = $(".user-statistic-list-form .search-form")
|
||||||
|
var exportLink = $(this);
|
||||||
|
var date = form.find("select[name='date']").val();
|
||||||
|
var schoolId = form.find('input[name="school_id"]').val();
|
||||||
|
|
||||||
|
var url = exportLink.data("url").split('?')[0] + "?date=" + date + "&school_id=" + schoolId;
|
||||||
|
window.open(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
class Admins::UserStatisticsController < Admins::BaseController
|
||||||
|
def index
|
||||||
|
default_sort('finish_shixun_count', 'desc')
|
||||||
|
|
||||||
|
total_count, users = Admins::UserStatisticQuery.call(params)
|
||||||
|
|
||||||
|
@users = paginate users, total_count: total_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def export
|
||||||
|
default_sort('finish_shixun_count', 'desc')
|
||||||
|
|
||||||
|
params[:per_page] = 10000
|
||||||
|
_count, @users = Admins::UserStatisticQuery.call(params)
|
||||||
|
|
||||||
|
filename = ['用户实训情况', Time.zone.now.strftime('%Y%m%d%H%M%S')].join('-') << '.xlsx'
|
||||||
|
render xlsx: 'export', filename: filename
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,7 @@
|
|||||||
|
class HotKeywordsController < ApplicationController
|
||||||
|
def index
|
||||||
|
keywords = []
|
||||||
|
keywords = HotSearchKeyword.hot(8) if HotSearchKeyword.available?
|
||||||
|
render_ok(keywords: keywords)
|
||||||
|
end
|
||||||
|
end
|
@ -1,5 +1,5 @@
|
|||||||
class MainController < ApplicationController
|
class MainController < ApplicationController
|
||||||
def index
|
def index
|
||||||
render file: 'public/react/build/index', formats: [:html]
|
render file: 'public/react/build/index.html', :layout => false
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -1,9 +1,12 @@
|
|||||||
class SearchsController < ApplicationController
|
class SearchsController < ApplicationController
|
||||||
|
after_action :record_search_keyword, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@results = SearchService.call(search_params)
|
@results = SearchService.call(search_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def search_params
|
def search_params
|
||||||
params.permit(:keyword, :type, :page, :per_page)
|
params.permit(:keyword, :type, :page, :per_page)
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
class Weapps::SearchsController < Weapps::BaseController
|
||||||
|
after_action :record_search_keyword, only: [:index]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@results = Weapps::SearchQuery.call(search_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def search_params
|
||||||
|
params.permit(:keyword, :type, :page, :per_page)
|
||||||
|
end
|
||||||
|
end
|
@ -1,9 +1,15 @@
|
|||||||
class ApplicationRecord < ActiveRecord::Base
|
class ApplicationRecord < ActiveRecord::Base
|
||||||
include NumberDisplayHelper
|
include NumberDisplayHelper
|
||||||
|
|
||||||
|
attr_accessor :_extra_data
|
||||||
|
|
||||||
self.abstract_class = true
|
self.abstract_class = true
|
||||||
|
|
||||||
def format_time(time)
|
def format_time(time)
|
||||||
time.present? ? time.strftime('%Y-%m-%d %H:%M') : ''
|
time.present? ? time.strftime('%Y-%m-%d %H:%M') : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def display_extra_data(key)
|
||||||
|
_extra_data&.[](key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,138 @@
|
|||||||
|
class Admins::UserStatisticQuery < ApplicationQuery
|
||||||
|
include CustomSortable
|
||||||
|
|
||||||
|
attr_reader :params
|
||||||
|
|
||||||
|
sort_columns :study_challenge_count, :finish_challenge_count, :study_shixun_count, :finish_shixun_count,
|
||||||
|
default_by: :finish_shixun_count, default_direction: :desc
|
||||||
|
|
||||||
|
def initialize(params)
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
users = User.where(type: 'User').group(:id)
|
||||||
|
|
||||||
|
users = users.joins(:user_extension).where(user_extensions: { school_id: params[:school_id] }) if params[:school_id].present?
|
||||||
|
|
||||||
|
total = users.count.count
|
||||||
|
|
||||||
|
# 根据排序字段进行查询
|
||||||
|
users = query_by_sort_column(users, params[:sort_by])
|
||||||
|
users = custom_sort(users, params[:sort_by], params[:sort_direction])
|
||||||
|
|
||||||
|
users = users.includes(user_extension: [:school, :department])
|
||||||
|
users = users.limit(page_size).offset(offset).to_a
|
||||||
|
# 查询并组装其它数据
|
||||||
|
users = package_other_data(users)
|
||||||
|
|
||||||
|
[total, users]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def package_other_data(users)
|
||||||
|
ids = users.map(&:id)
|
||||||
|
|
||||||
|
study_myshixun = Myshixun.where(user_id: ids)
|
||||||
|
finish_myshixun = Myshixun.where(user_id: ids, status: 1)
|
||||||
|
study_challenge = Game.joins(:myshixun).where(myshixuns: { user_id: ids }).where(status: [0, 1, 2])
|
||||||
|
finish_challenge = Game.joins(:myshixun).where(myshixuns: { user_id: ids }).where(status: 2)
|
||||||
|
|
||||||
|
if time_range.present?
|
||||||
|
study_myshixun = study_myshixun.where(updated_at: time_range)
|
||||||
|
finish_myshixun = finish_myshixun.where(updated_at: time_range)
|
||||||
|
study_challenge = study_challenge.where(updated_at: time_range)
|
||||||
|
finish_challenge = finish_challenge.where(updated_at: time_range)
|
||||||
|
end
|
||||||
|
|
||||||
|
study_myshixun_map = study_myshixun.group(:user_id).count
|
||||||
|
finish_myshixun_map = finish_myshixun.group(:user_id).count
|
||||||
|
study_challenge_map = study_challenge.group(:user_id).count
|
||||||
|
finish_challenge_map = finish_challenge.group(:user_id).count
|
||||||
|
|
||||||
|
users.each do |user|
|
||||||
|
user._extra_data = {
|
||||||
|
study_shixun_count: study_myshixun_map.fetch(user.id, 0),
|
||||||
|
finish_shixun_count: finish_myshixun_map.fetch(user.id, 0),
|
||||||
|
study_challenge_count: study_challenge_map.fetch(user.id, 0),
|
||||||
|
finish_challenge_count: finish_challenge_map.fetch(user.id, 0),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
users
|
||||||
|
end
|
||||||
|
|
||||||
|
def query_by_sort_column(users, sort_by_column)
|
||||||
|
base_query_column = 'users.*'
|
||||||
|
|
||||||
|
case sort_by_column.to_s
|
||||||
|
when 'study_shixun_count' then
|
||||||
|
users =
|
||||||
|
if time_range.present?
|
||||||
|
users.joins("LEFT JOIN myshixuns ON myshixuns.user_id = users.id "\
|
||||||
|
"AND myshixuns.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'")
|
||||||
|
else
|
||||||
|
users.left_joins(:myshixuns)
|
||||||
|
end
|
||||||
|
|
||||||
|
users.select("#{base_query_column}, COUNT(*) study_shixun_count")
|
||||||
|
when 'finish_shixun_count' then
|
||||||
|
users =
|
||||||
|
if time_range.present?
|
||||||
|
users.joins("LEFT JOIN myshixuns ON myshixuns.user_id = users.id AND myshixuns.status = 1 AND "\
|
||||||
|
"myshixuns.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'")
|
||||||
|
else
|
||||||
|
users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id AND myshixuns.status = 1')
|
||||||
|
end
|
||||||
|
|
||||||
|
users.select("#{base_query_column}, COUNT(*) finish_shixun_count")
|
||||||
|
when 'study_challenge_count' then
|
||||||
|
users =
|
||||||
|
if time_range.present?
|
||||||
|
users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id')
|
||||||
|
.joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id "\
|
||||||
|
"AND games.status IN (0,1,2) AND games.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'")
|
||||||
|
else
|
||||||
|
users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id')
|
||||||
|
.joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id AND games.status IN (0,1,2)")
|
||||||
|
end
|
||||||
|
|
||||||
|
users.select("#{base_query_column}, COUNT(*) study_challenge_count")
|
||||||
|
when 'finish_challenge_count' then
|
||||||
|
users =
|
||||||
|
if time_range.present?
|
||||||
|
users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id')
|
||||||
|
.joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id "\
|
||||||
|
"AND games.status = 2 AND games.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'")
|
||||||
|
else
|
||||||
|
users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id')
|
||||||
|
.joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id AND games.status = 2")
|
||||||
|
end
|
||||||
|
|
||||||
|
users.select("#{base_query_column}, COUNT(*) finish_challenge_count")
|
||||||
|
else
|
||||||
|
users
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_range
|
||||||
|
@_time_range ||= begin
|
||||||
|
case params[:date]
|
||||||
|
when 'weekly' then 1.weeks.ago..Time.now
|
||||||
|
when 'monthly' then 1.months.ago..Time.now
|
||||||
|
when 'quarterly' then 3.months.ago..Time.now
|
||||||
|
when 'yearly' then 1.years.ago..Time.now
|
||||||
|
else ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_size
|
||||||
|
params[:per_page].to_i.zero? ? 20 : params[:per_page].to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def offset
|
||||||
|
(params[:page].to_i.zero? ? 0 : params[:page].to_i - 1) * page_size
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,37 @@
|
|||||||
|
class Weapps::SearchQuery < ApplicationQuery
|
||||||
|
include ElasticsearchAble
|
||||||
|
|
||||||
|
attr_reader :params
|
||||||
|
|
||||||
|
def initialize(params)
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
modal_name.search(keyword, search_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def search_options
|
||||||
|
hash = {
|
||||||
|
fields: [:name],
|
||||||
|
page: page,
|
||||||
|
per_page: per_page
|
||||||
|
}
|
||||||
|
hash.merge(where: { status: 2 }) if modal_name == Shixun
|
||||||
|
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def modal_name
|
||||||
|
@_modal_name ||= begin
|
||||||
|
case params[:type].to_s
|
||||||
|
when 'subject' then Subject
|
||||||
|
when 'shixun' then Shixun
|
||||||
|
when 'course' then Course
|
||||||
|
else Subject
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,16 @@
|
|||||||
|
wb = xlsx_package.workbook
|
||||||
|
wb.add_worksheet(name: '用户实训情况') do |sheet|
|
||||||
|
sheet.add_row %w(姓名 单位部门 学习关卡数 完成关卡数 学习实训数 完成实训数)
|
||||||
|
|
||||||
|
@users.each do |user|
|
||||||
|
data = [
|
||||||
|
user.real_name,
|
||||||
|
[user.school_name.presence, user.department_name.presence].compact.join(' - '),
|
||||||
|
user.display_extra_data(:study_challenge_count),
|
||||||
|
user.display_extra_data(:finish_challenge_count),
|
||||||
|
user.display_extra_data(:study_shixun_count),
|
||||||
|
user.display_extra_data(:finish_shixun_count)
|
||||||
|
]
|
||||||
|
sheet.add_row(data)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,27 @@
|
|||||||
|
<% define_admin_breadcrumbs do %>
|
||||||
|
<% add_admin_breadcrumb('用户实训情况') %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="box search-form-container user-statistic-list-form">
|
||||||
|
<%= form_tag(admins_user_statistics_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
|
||||||
|
<div class="form-group col-12 col-md-auto">
|
||||||
|
<label for="status">时间范围:</label>
|
||||||
|
<% data_arrs = [['不限', ''], ['最近一周', 'weekly'], ['最近一个月', 'monthly'], ['最近三个月', 'quarterly'], ['最近一年', 'yearly']] %>
|
||||||
|
<%= select_tag(:date, options_for_select(data_arrs, params[:date]), class: 'form-control') %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-12 col-md-3">
|
||||||
|
<label for="school_name">所属单位:</label>
|
||||||
|
<%= hidden_field_tag(:school_id, params[:school_id]) %>
|
||||||
|
<%= select_tag :school_name, options_for_select([''], params[:school_id]), class: 'form-control school-select flex-1' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
|
||||||
|
<input type="reset" class="btn btn-secondary clear-btn" value="清空"/>
|
||||||
|
<% end %>
|
||||||
|
<%= javascript_void_link '导出', class: 'btn btn-outline-primary export-action', 'data-url': export_admins_user_statistics_path(format: :xlsx) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box user-statistic-list-container">
|
||||||
|
<%= render partial: 'admins/user_statistics/shared/list', locals: { users: @users } %>
|
||||||
|
</div>
|
@ -0,0 +1 @@
|
|||||||
|
$('.user-statistic-list-container').html("<%= j( render partial: 'admins/user_statistics/shared/list', locals: { users: @users } ) %>");
|
@ -0,0 +1,34 @@
|
|||||||
|
<table class="table table-hover text-center user-statistic-list-table">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th width="14%" class="text-left">姓名</th>
|
||||||
|
<th width="38%" class="text-left">单位部门</th>
|
||||||
|
<th width="12%"><%= sort_tag('学习关卡数', name: 'study_challenge_count', path: admins_user_statistics_path) %></th>
|
||||||
|
<th width="12%"><%= sort_tag('完成关卡数', name: 'finish_challenge_count', path: admins_user_statistics_path) %></th>
|
||||||
|
<th width="12%"><%= sort_tag('学习实训数', name: 'study_shixun_count', path: admins_user_statistics_path) %></th>
|
||||||
|
<th width="12%"><%= sort_tag('完成实训数', name: 'finish_shixun_count', path: admins_user_statistics_path) %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% if users.present? %>
|
||||||
|
<% users.each do |user| %>
|
||||||
|
<tr class="user-statistic-item-<%= user.id %>">
|
||||||
|
<td class="text-left">
|
||||||
|
<%= link_to "/users/#{user.login}", target: '_blank' do %>
|
||||||
|
<%= overflow_hidden_span user.real_name, width: 100 %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td class="text-left"><%= display_text [user.school_name.presence, user.department_name.presence].compact.join(' - ') %></td>
|
||||||
|
<td><%= user.display_extra_data(:study_challenge_count) %></td>
|
||||||
|
<td><%= user.display_extra_data(:finish_challenge_count) %></td>
|
||||||
|
<td><%= user.display_extra_data(:study_shixun_count) %></td>
|
||||||
|
<td><%= user.display_extra_data(:finish_shixun_count) %></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,9 @@
|
|||||||
|
json.count @results.total_count
|
||||||
|
json.results do
|
||||||
|
json.array! @results.with_highlights(multiple: true) do |obj, highlights|
|
||||||
|
json.merge! obj.to_searchable_json
|
||||||
|
json.type obj.class.name.downcase
|
||||||
|
|
||||||
|
json.title highlights.delete(:name)&.join('...') || obj.searchable_title
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Loading…
Reference in new issue