parent
							
								
									120f59aca7
								
							
						
					
					
						commit
						416ef01528
					
				@ -0,0 +1,122 @@
 | 
				
			||||
$(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 matcherProvinceFunc = function(params, data){
 | 
				
			||||
          if ($.trim(params.term) === '') {
 | 
				
			||||
              return data;
 | 
				
			||||
          }
 | 
				
			||||
          if (typeof data.text === 'undefined') {
 | 
				
			||||
              return null;
 | 
				
			||||
          }
 | 
				
			||||
 | 
				
			||||
          if (data.province && data.province.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
 | 
				
			||||
      });
 | 
				
			||||
    };
 | 
				
			||||
 | 
				
			||||
      var defineProvinceSelect = function (province) {
 | 
				
			||||
          $form.find('.province-select').select2({
 | 
				
			||||
              theme: 'bootstrap4',
 | 
				
			||||
              placeholder: '选择省份',
 | 
				
			||||
              minimumInputLength: 1,
 | 
				
			||||
              data: province,
 | 
				
			||||
              templateResult: function (item) {
 | 
				
			||||
                  if(!item.province || item.province === '') return item.text;
 | 
				
			||||
                  return item.province;
 | 
				
			||||
              },
 | 
				
			||||
              templateSelection: function(item){
 | 
				
			||||
                  if (item.province) {
 | 
				
			||||
                      $form.find('#province').val(item.province);
 | 
				
			||||
                  }
 | 
				
			||||
                  return item.province || item.text;
 | 
				
			||||
              },
 | 
				
			||||
              matcher: matcherProvinceFunc
 | 
				
			||||
          });
 | 
				
			||||
      };
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
    // 初始化学校选择器
 | 
				
			||||
    $.ajax({
 | 
				
			||||
      url: '/api/schools/for_option.json',
 | 
				
			||||
      dataType: 'json',
 | 
				
			||||
      type: 'GET',
 | 
				
			||||
      success: function(data) {
 | 
				
			||||
        defineSchoolSelect(data.schools);
 | 
				
			||||
      }
 | 
				
			||||
    });
 | 
				
			||||
 | 
				
			||||
      // 初始化身份选择器
 | 
				
			||||
      $.ajax({
 | 
				
			||||
          url: '/api/schools/for_province_option.json',
 | 
				
			||||
          dataType: 'json',
 | 
				
			||||
          type: 'GET',
 | 
				
			||||
          success: function (data) {
 | 
				
			||||
              console.log(data.province);
 | 
				
			||||
              defineProvinceSelect(data.province);
 | 
				
			||||
          }
 | 
				
			||||
      });
 | 
				
			||||
 | 
				
			||||
    // 清空
 | 
				
			||||
    $form.on('click', '.clear-btn', function(){
 | 
				
			||||
      $form.find('select[name="date"]').val('');
 | 
				
			||||
      $form.find('.school-select').val('').trigger('change');
 | 
				
			||||
      $form.find('.province-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, @users = 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.where(nil)
 | 
				
			||||
    if params[:province]
 | 
				
			||||
      schools = schools.where("province like ?", "%#{params[:province]}%")
 | 
				
			||||
    end
 | 
				
			||||
 | 
				
			||||
    if params[:school_id]
 | 
				
			||||
      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 = t.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,34 @@
 | 
				
			||||
<% 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="province">所属省份:</label>
 | 
				
			||||
      <%= hidden_field_tag(:province, params[:province]) %>
 | 
				
			||||
      <%= select_tag :province, options_for_select([''], params[:province]), class: 'form-control province-select flex-1' %>
 | 
				
			||||
    </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_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 } %>
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue