Merge branch 'dev_aliyun' of http://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun
commit
5bc4b58755
@ -0,0 +1,75 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.admins-user-schools-statistics-index-page').length > 0) {
|
||||
var $form = $('.user-schools-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('select[name="province"]').val('');
|
||||
$form.find('.school-select').val('').trigger('change');
|
||||
$form.find('input[type="submit"]').trigger('click');
|
||||
})
|
||||
|
||||
|
||||
// 导出
|
||||
$('.export-action').on('click', function(){
|
||||
var form = $(".user-schools-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,18 @@
|
||||
class Admins::UserSchoolsStatisticsController < Admins::BaseController
|
||||
|
||||
def export
|
||||
params[:per_page] = 10000
|
||||
_count, @schools = Admins::UserSchoolsStatisticQuery.call(params)
|
||||
|
||||
filename = ['用户运营统计', Time.zone.now.strftime('%Y%m%d%H%M%S')].join('-') << '.xlsx'
|
||||
render xlsx: 'export', filename: filename
|
||||
end
|
||||
|
||||
def index
|
||||
default_sort('finish_challenge_count', 'desc')
|
||||
total_count, schools = Admins::UserSchoolsStatisticQuery.call(params)
|
||||
|
||||
@schools = paginate schools, total_count: total_count
|
||||
|
||||
end
|
||||
end
|
@ -0,0 +1,160 @@
|
||||
class Admins::UserSchoolsStatisticQuery < ApplicationQuery
|
||||
include CustomSortable
|
||||
|
||||
attr_reader :params
|
||||
|
||||
sort_columns :study_challenge_count, :finish_challenge_count, :study_shixun_count, :finish_shixun_count,
|
||||
default_by: :finish_challenge_count, default_direction: :desc
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
schools = School.all
|
||||
if params[:province].present?
|
||||
schools = schools.where("province like ?", "%#{params[:province]}%")
|
||||
end
|
||||
|
||||
if params[:school_id].present?
|
||||
schools = schools.where(id: params[:school_id])
|
||||
end
|
||||
|
||||
total = schools.count
|
||||
# 根据排序字段进行查询
|
||||
#schools = query_by_sort_column(schools, params[:sort_by])
|
||||
#schools = custom_sort(schools, params[:sort_by], params[:sort_direction])
|
||||
#
|
||||
schools = schools.limit(page_size).offset(offset).to_a
|
||||
|
||||
# 查询并组装其它数据
|
||||
schools = package_other_data(schools)
|
||||
|
||||
[total, schools]
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def package_other_data(schools)
|
||||
ids = schools.map(&:id)
|
||||
user_e = UserExtension.where(school_id: schools.map(&:id))
|
||||
#study_myshixun = Myshixun.joins("join user_extensions ue on ue.user_id = myshixuns.user_id").where(ue: {school_id: ids})
|
||||
#finish_myshixun = Myshixun.joins("join user_extensions ue on ue.user_id = myshixuns.user_id")
|
||||
# .where(ue: {school_id: ids}, myshixuns: {status: 1})
|
||||
study_challenge = Game.joins("join user_extensions ue on ue.user_id = games.user_id")
|
||||
.where(ue: {school_id: ids},).where( games:{status: [0, 1, 2]})
|
||||
finish_challenge = Game.joins("join user_extensions ue on ue.user_id = games.user_id")
|
||||
.where(ue: {school_id: ids}).where(games: {status: 2})
|
||||
reg_teacher = user_e.where(identity: 'teacher')
|
||||
reg_student = user_e.where.not(identity: 'teacher')
|
||||
|
||||
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)
|
||||
reg_teacher = reg_teacher.where(created_at: time_range)
|
||||
reg_student = reg_student.where(created_at: time_range)
|
||||
user_e = user_e.joins(:user).where(users: {last_login_on: time_range})
|
||||
end
|
||||
|
||||
#study_myshixun_map = study_myshixun.reorder(nil).group(:school_id).count
|
||||
#finish_myshixun_map = finish_myshixun.reorder(nil).group(:school_id).count
|
||||
study_challenge_map = study_challenge.reorder(nil).group(:school_id).count
|
||||
finish_challenge_map = finish_challenge.reorder(nil).group(:school_id).count
|
||||
evaluate_count_map = study_challenge.reorder(nil).group(:school_id).sum(:evaluate_count)
|
||||
reg_teacher_map = reg_teacher.reorder(nil).group(:school_id).count
|
||||
reg_student_map = reg_student.reorder(nil).group(:school_id).count
|
||||
user_e_map = user_e.reorder(nil).group(:school_id).count
|
||||
|
||||
schools.each do |school|
|
||||
school._extra_data = {
|
||||
#study_shixun_count: study_myshixun_map.fetch(schools.id, 0),
|
||||
#finish_shixun_count: finish_myshixun_map.fetch(schools.id, 0),
|
||||
study_challenge_count: study_challenge_map.fetch(school.id, 0),
|
||||
finish_challenge_count: finish_challenge_map.fetch(school.id, 0),
|
||||
evaluate_count: evaluate_count_map.fetch(school.id, 0),
|
||||
reg_teacher_count: reg_teacher_map.fetch(school.id, 0),
|
||||
reg_student_count: reg_student_map.fetch(school.id, 0),
|
||||
user_active_count: user_e_map.fetch(school.id, 0)
|
||||
}
|
||||
end
|
||||
|
||||
schools
|
||||
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.user_id = users.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.user_id = users.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 'dayly' then 1.days.ago..Time.now
|
||||
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,18 @@
|
||||
wb = xlsx_package.workbook
|
||||
wb.add_worksheet(name: '用户运营统计') do |sheet|
|
||||
sheet.add_row %w(单位名称 省份 注册老师数量 注册学生数量 活跃用户 学习关卡数 完成关卡数 评测次数)
|
||||
|
||||
@schools.each do |school|
|
||||
data = [
|
||||
school.name,
|
||||
school.province,
|
||||
school.display_extra_data(:reg_teacher_count),
|
||||
school.display_extra_data(:reg_student_count),
|
||||
school.display_extra_data(:user_active_count),
|
||||
school.display_extra_data(:study_challenge_count),
|
||||
school.display_extra_data(:finish_challenge_count),
|
||||
school.display_extra_data(:evaluate_count),
|
||||
]
|
||||
sheet.add_row(data)
|
||||
end
|
||||
end
|
@ -0,0 +1,30 @@
|
||||
<% define_admin_breadcrumbs do %>
|
||||
<% add_admin_breadcrumb('用户运营情况') %>
|
||||
<% end %>
|
||||
|
||||
<div class="box search-form-container user-schools-statistic-list-form">
|
||||
<%= form_tag(admins_user_schools_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 = [['不限', ''],['最近一天', 'dayly'], ['最近一周', '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>
|
||||
|
||||
<%= text_field_tag(:province, params[:province], class: 'form-control col-sm-2 ml-3', placeholder: '所属省份') %>
|
||||
|
||||
<%= 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_schools_statistics_path(format: :xlsx) %>
|
||||
</div>
|
||||
|
||||
<div class="box admin-list-container user-schools-statistic-list-container">
|
||||
<%= render partial: 'admins/user_schools_statistics/shared/list', locals: { schools: @schools } %>
|
||||
</div>
|
@ -0,0 +1 @@
|
||||
$('.user-schools-statistic-list-container').html("<%= j( render partial: 'admins/user_schools_statistics/shared/list', locals: { schools: @schools } ) %>");
|
@ -0,0 +1,38 @@
|
||||
<table class="table table-hover text-center user-statistic-list-table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th width="6%">序号</th>
|
||||
<th width="22%" class="text-left">单位名称</th>
|
||||
<th width="8%" class="text-left">省份</th>
|
||||
<th width="10%">注册老师数量<%#= sort_tag('学习关卡数', name: 'study_challenge_count', path: admins_user_statistics_path) %></th>
|
||||
<th width="10%">注册学生数量<%#= sort_tag('完成关卡数', name: 'finish_challenge_count', path: admins_user_statistics_path) %></th>
|
||||
<th width="10%">活跃用户<%#= sort_tag('活跃用户', name: 'user_active_count', path: admins_user_schools_statistics_path) %></th>
|
||||
<th width="10%">学习关卡数<%#= sort_tag('学习关卡数', name: 'finish_shixun_count', path: admins_user_schools_statistics_path) %></th>
|
||||
<th width="10%">完成关卡数</th>
|
||||
<th width="14%">评测次数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if schools.present? %>
|
||||
<% schools.each_with_index do |school, index| %>
|
||||
<tr class="user-statistic-item-<%= school.id %>">
|
||||
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
|
||||
<td class="text-left">
|
||||
<%= school.name %>
|
||||
</td>
|
||||
<td class="text-left"><%= school.province %></td>
|
||||
<td><%= school.display_extra_data(:reg_teacher_count) %></td>
|
||||
<td><%= school.display_extra_data(:reg_student_count) %></td>
|
||||
<td><%= school.display_extra_data(:user_active_count) %></td>
|
||||
<td><%= school.display_extra_data(:study_challenge_count) %></td>
|
||||
<td><%= school.display_extra_data(:finish_challenge_count) %></td>
|
||||
<td><%= school.display_extra_data(:evaluate_count) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= render 'admins/shared/no_data_for_table' %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<%= render partial: 'admins/shared/paginate', locals: { objects: schools } %>
|
File diff suppressed because one or more lines are too long
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.
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.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
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 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.
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.
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.
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 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