educode_sales 0.9.2 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/educode_sales/application_controller.rb +3 -1
  3. data/app/controllers/educode_sales/sale_trends_controller.rb +266 -253
  4. data/app/controllers/educode_sales/sessions_controller.rb +1 -0
  5. data/app/controllers/educode_sales/user_behaviors_controller.rb +26 -0
  6. data/app/models/educode_sales/login_history.rb +5 -0
  7. data/app/models/educode_sales/sale_trend.rb +2 -0
  8. data/app/models/educode_sales/staff.rb +22 -0
  9. data/app/views/educode_sales/sale_trends/_business_area.html.erb +83 -0
  10. data/app/views/educode_sales/sale_trends/_business_followup_analysis.html.erb +110 -0
  11. data/app/views/educode_sales/sale_trends/_sales_analysis.html.erb +121 -0
  12. data/app/views/educode_sales/sale_trends/_sales_followup_analysis.html.erb +106 -0
  13. data/app/views/educode_sales/sale_trends/business_area.js.erb +1 -0
  14. data/app/views/educode_sales/sale_trends/business_followup_analysis.js.erb +1 -0
  15. data/app/views/educode_sales/sale_trends/sales_analysis.js.erb +1 -0
  16. data/app/views/educode_sales/sale_trends/sales_followup_analysis.js.erb +1 -0
  17. data/app/views/educode_sales/sale_trends/trends.html.erb +214 -548
  18. data/app/views/educode_sales/user_behaviors/index.html.erb +80 -0
  19. data/app/views/educode_sales/user_behaviors/index.json.jbuilder +13 -0
  20. data/app/views/layouts/educode_sales/application.html.erb +6 -1
  21. data/config/routes.rb +5 -1
  22. data/db/migrate/20220827130438_create_educode_sales_login_histories.rb +13 -0
  23. data/lib/educode_sales/version.rb +1 -1
  24. metadata +19 -7
  25. data/app/assets/images/educode_sales/indexlogo.png +0 -0
@@ -1,4 +1,6 @@
1
1
  module EducodeSales
2
2
  class SaleTrend < ApplicationRecord
3
+
4
+ COLORS = ['#44D7B6', '#4CACFF', '#F7B500', '#FF0000', '#FF7F00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#8B00FF', 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)', 'rgba(255, 0, 250, 1)']
3
5
  end
4
6
  end
@@ -15,12 +15,34 @@ module EducodeSales
15
15
  has_many :sale_plans, dependent: :destroy
16
16
  has_many :staff_schools, dependent: :destroy
17
17
 
18
+ has_many :login_histories, dependent: :destroy
19
+
18
20
  has_many :market_areas, dependent: :destroy
19
21
  has_many :areas, through: :market_areas
20
22
  validates :user_id, uniqueness: { message: '已存在' }
21
23
 
22
24
  # attr_writer :month
23
25
 
26
+ def last_login_key
27
+ "login_user_#{self.id}"
28
+ end
29
+
30
+ def check_login_status(request)
31
+ unless Rails.cache.data.get(self.last_login_key)
32
+ # 第二天还在线访问,记用户登录一次
33
+ create_login_history(request)
34
+ end
35
+ end
36
+
37
+ def create_login_history(request)
38
+ # 登录状态保存到当天结束时间后+10分钟
39
+ Rails.cache.data.set(self.last_login_key, 1)
40
+ Rails.cache.data.expireat(self.last_login_key, Time.now.end_of_day.to_i + 600)
41
+
42
+ last_history = self.login_histories.last
43
+ self.login_histories.create(current_ip: request.remote_ip, last_ip: last_history&.current_ip, last_login_at: last_history&.created_at)
44
+ end
45
+
24
46
  def self.month_list
25
47
  list = []
26
48
  24.times.map do |d|
@@ -0,0 +1,83 @@
1
+ <div class="" style="padding-right: 50px" id="tab_4">
2
+ <div class=" layui-show" style="padding-left: 20px">
3
+ <div style="margin: 10px 10px 10px 10px">
4
+ <form class="layui-form layui-form-pane" lay-filter="search_form">
5
+ <div class="layui-form-item">
6
+ <div class="layui-inline m-t-10">
7
+ <label class="layui-form-label">维度</label>
8
+ <div class="layui-input-inline">
9
+ <%= select_tag "business_count_type", options_for_select([['按商机总额统计','money'],['按商机数量统计','count']],params[:business_count_type]), {'lay-filter': 'goal_count'}%>
10
+ </div>
11
+ </div>
12
+ <div class="layui-inline m-t-10">
13
+ <label class="layui-form-label">商机类型</label>
14
+ <div class="layui-input-inline">
15
+ <%= select_tag "business_type", options_for_select(EducodeSales::Common.where(clazz: 'business_type').where.not(extras: "x_class").pluck(:name, :id), params[:business_type]), { 'lay-filter': 'clazz_id', include_blank: true } %>
16
+ </div>
17
+ </div>
18
+ <div class="layui-inline">
19
+ <button type="button" id="search_bt" class="business_bt layui-btn layui-btn-primary" lay-submit lay-filter="business_area">确定
20
+ </button>
21
+ </div>
22
+ </div>
23
+ </form>
24
+ </div>
25
+ <canvas id="myChart3" width="960" height="200"></canvas>
26
+ </div>
27
+
28
+ </div>
29
+ <script>
30
+
31
+ layui.use(['form', 'jquery', 'request', 'element'], function () {
32
+ form = layui.form;
33
+
34
+ var opt = {
35
+ events: false,
36
+ tooltips: {
37
+ enabled: false
38
+ },
39
+ hover: {
40
+ animationDuration: 0
41
+ },
42
+ animation: {
43
+ duration: 1,
44
+ onComplete: function () {
45
+ var chartInstance = this.chart,
46
+ ctx = chartInstance.ctx;
47
+ ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
48
+ ctx.textAlign = 'center';
49
+ ctx.textBaseline = 'bottom';
50
+
51
+ this.data.datasets.forEach(function (dataset, i) {
52
+ var meta = chartInstance.controller.getDatasetMeta(i);
53
+ meta.data.forEach(function (bar, index) {
54
+ var data = dataset.data[index];
55
+ ctx.fillText(data, bar._model.x, bar._model.y - 5);
56
+ });
57
+ });
58
+ }
59
+ }
60
+ };
61
+
62
+
63
+ var ctx = document.getElementById('myChart3');
64
+ var myChart3 = new Chart(ctx, {
65
+ type: 'bar',
66
+ data: <%=raw @business_data.to_json %>,
67
+ options: opt
68
+ });
69
+ form.render();
70
+ form.on("submit(business_area)", function(data) {
71
+ var layer_index = layer.load(0, {shade: [0.1, '#fff']});
72
+ Rails.ajax({
73
+ url: '/missions/sale_trends/business_area',
74
+ type: 'GET',
75
+ data: $.param(data.field, true),
76
+ dataType: "script",
77
+ success: function (res) {
78
+ layer.close(layer_index);
79
+ }
80
+ });
81
+ })
82
+ })
83
+ </script>
@@ -0,0 +1,110 @@
1
+ <div class="" style="padding-right: 50px" id="tab_2">
2
+ <div class="" style="padding-left: 20px">
3
+ <div style="margin: 10px 10px 10px 10px">
4
+ <form class="layui-form layui-form-pane" lay-filter="search_business_form">
5
+ <div class="layui-form-item">
6
+ <div class="layui-inline m-t-10">
7
+ <label class="layui-form-label">视图</label>
8
+ <div class="layui-input-inline">
9
+ <%= select_tag "follow_count_range", options_for_select([['按天','day'],['按周','week'],['按月','month'],['按年','year']],params[:follow_count_range] || 'week'), {'lay-filter': 'follow_count'}%>
10
+ </div>
11
+ </div>
12
+ <div class="layui-inline">
13
+ <label class="layui-form-label">时间范围</label>
14
+ <div class="layui-input-inline">
15
+ <input type="text" class="layui-input month layui-hide" id="date_month" name="date_month" placeholder=" - " value="<%=params[:date_month] %>" autocomplete="off" >
16
+ <input type="text" class="layui-input year layui-hide" id="date_year" name="date_year" placeholder=" - " value="<%=params[:date_year] %>" autocomplete="off">
17
+ <input type="text" class="layui-input week " id="date_week" name="date_week" placeholder=" - " value="<%=params[:date_week].present? ? params[:date_week] : (Time.now - 30.days).beginning_of_week.to_s(:date) + ' - ' + Time.now.end_of_week.to_s(:date) %>" autocomplete="off">
18
+ <input type="text" class="layui-input day layui-hide" id="date" name="date" placeholder=" - " value="<%=params[:date] %>" autocomplete="off">
19
+ </div>
20
+ </div>
21
+ <div class="layui-inline">
22
+ <button type="button" id="search_bt" class=" follow_count_bt layui-btn layui-btn-primary" lay-submit lay-filter="search_bussiness">确定</button>
23
+ </div>
24
+ </div>
25
+ </form>
26
+ </div>
27
+ <canvas id="myChart" width="960" height="200"></canvas>
28
+ </div>
29
+ </div>
30
+ <script>
31
+ layui.use(['form', 'jquery', 'request', 'element'], function () {
32
+ form = layui.form;
33
+ var ctx = document.getElementById('myChart');
34
+ myChart = new Chart(ctx, {
35
+ type: 'line',
36
+ data: <%=raw @follow_count_data.to_json %>,
37
+ options: {
38
+ elements: {
39
+ line: {
40
+ tension: 0
41
+ }
42
+ }
43
+ }
44
+ });
45
+ form.render();
46
+ laydate.render({
47
+ elem: '#date',
48
+ range: true
49
+ });
50
+
51
+ laydate.render({
52
+ elem: '#date_week',
53
+ range: true
54
+ });
55
+ laydate.render({
56
+ type: 'month',
57
+ elem: '#date_month',
58
+ range: true
59
+ });
60
+ laydate.render({
61
+ type: 'year',
62
+ elem: '#date_year',
63
+ range: true
64
+ });
65
+ form.on('select(follow_count)', function(data){
66
+ var value = data.value;
67
+ if(value == "month"){
68
+ $(".year").addClass('layui-hide')
69
+ $(".day").addClass('layui-hide')
70
+ $(".week").addClass('layui-hide')
71
+ $(".month").removeClass('layui-hide')
72
+ }else if(value == "year"){
73
+ $(".month").addClass('layui-hide')
74
+ $(".day").addClass('layui-hide')
75
+ $(".week").addClass('layui-hide')
76
+ $(".year").removeClass('layui-hide')
77
+ }else if(value == "week"){
78
+ $(".month").addClass('layui-hide')
79
+ $(".day").addClass('layui-hide')
80
+ $(".week").removeClass('layui-hide')
81
+ $(".year").addClass('layui-hide')
82
+ }else {
83
+ $(".month").addClass('layui-hide')
84
+ $(".year").addClass('layui-hide')
85
+ $(".day").removeClass('layui-hide')
86
+ $(".week").addClass('layui-hide')
87
+
88
+ }
89
+ })
90
+ form.on("submit(search_bussiness)", function(data) {
91
+ var layer_index = layer.load(0, {shade: [0.1, '#fff']});
92
+ Rails.ajax({
93
+ url: '/missions/sale_trends/business_followup_analysis',
94
+ type: 'GET',
95
+ dataType: "json",
96
+ data: $.param(data.field, true),
97
+ success: function (res) {
98
+ layer.close(layer_index);
99
+ if (res.success == false) {
100
+ layer.alert(res.msg)
101
+ } else {
102
+ myChart.data.datasets = res.data.datasets;
103
+ myChart.data.labels = res.data.labels;
104
+ myChart.update()
105
+ }
106
+ }
107
+ });
108
+ })
109
+ })
110
+ </script>
@@ -0,0 +1,121 @@
1
+ <div id="tab_3" lay-filter="test1">
2
+ <div class="" style="padding-right: 50px">
3
+ <div style="margin: 10px 10px 10px 10px">
4
+ <form class="layui-form layui-form-pane" lay-filter="search_form">
5
+ <div class="layui-form-item">
6
+ <div class="layui-inline m-t-10">
7
+ <label class="layui-form-label">维度</label>
8
+ <div class="layui-input-inline">
9
+ <%= select_tag "count_type", options_for_select([['按合同额统计','actual_amount'],['按总额统计','total_amount']],params[:count_type]), {'lay-filter': 'count_type'}%>
10
+ </div>
11
+ </div>
12
+ <div class="layui-inline m-t-10">
13
+ <label class="layui-form-label">视图</label>
14
+ <div class="layui-input-inline">
15
+ <%= select_tag "goal_count_range", options_for_select([['按月','month'],['按年','year']],params[:goal_count_range]), {'lay-filter': 'goal_count'}%>
16
+ </div>
17
+ </div>
18
+ <div class="layui-inline">
19
+ <label class="layui-form-label">时间范围</label>
20
+ <div class="layui-input-inline">
21
+ <input type="text" class="layui-input goal_month " id="goal_date_month" name="goal_date_month" placeholder=" - " value="<%=params[:goal_date_month].present? ? params[:goal_date_month] : Time.now.year.to_s + "-01" + " - " + Time.now.strftime("%Y-%m") %>" autocomplete="off">
22
+ <input type="text" class="layui-input goal_year layui-hide" id="goal_date_year" name="goal_date_year" placeholder=" - " value="<%=params[:goal_date_year] %>" autocomplete="off">
23
+ </div>
24
+ </div>
25
+ <div class="layui-inline">
26
+ <button type="button" id="search_bt" class="goal_count_bt layui-btn layui-btn-primary" lay-submit lay-filter="search_sales">确定
27
+ </button>
28
+ </div>
29
+ </div>
30
+ </form>
31
+ </div>
32
+ <canvas id="myChart2" width="960" height="200"></canvas>
33
+
34
+ </div>
35
+ </div>
36
+ <script>
37
+ var goal_type = "<%=@goal_count_range %>";
38
+ layui.use(['form', 'jquery', 'request', 'element'], function () {
39
+ form = layui.form;
40
+
41
+ var opt = {
42
+ events: false,
43
+ tooltips: {
44
+ enabled: false
45
+ },
46
+ hover: {
47
+ animationDuration: 0
48
+ },
49
+ animation: {
50
+ duration: 1,
51
+ onComplete: function () {
52
+ var chartInstance = this.chart,
53
+ ctx = chartInstance.ctx;
54
+ ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
55
+ ctx.textAlign = 'center';
56
+ ctx.textBaseline = 'bottom';
57
+
58
+ this.data.datasets.forEach(function (dataset, i) {
59
+ var meta = chartInstance.controller.getDatasetMeta(i);
60
+ meta.data.forEach(function (bar, index) {
61
+ var data = dataset.data[index];
62
+ ctx.fillText(data, bar._model.x, bar._model.y - 5);
63
+ });
64
+ });
65
+ }
66
+ }
67
+ };
68
+
69
+ laydate.render({
70
+ type: 'year',
71
+ elem: '#goal_date_year',
72
+ range: true
73
+ });
74
+ laydate.render({
75
+ type: 'month',
76
+ elem: '#goal_date_month',
77
+ range: true
78
+ });
79
+
80
+ if(goal_type == "year"){
81
+ $(".goal_year").removeClass('layui-hide')
82
+ $(".goal_month").addClass('layui-hide')
83
+ }else{
84
+ $(".goal_year").addClass('layui-hide')
85
+ $(".goal_month").removeClass('layui-hide')
86
+ }
87
+
88
+ form.on('select(goal_count)', function(data){
89
+ const value = data.value;
90
+ if (value == "month") {
91
+ $(".goal_year").addClass('layui-hide')
92
+ $(".goal_month").removeClass('layui-hide')
93
+ } else {
94
+ $(".goal_month").addClass('layui-hide')
95
+ $(".goal_year").removeClass('layui-hide')
96
+ }
97
+ })
98
+
99
+
100
+
101
+ var ctx = document.getElementById('myChart2');
102
+ var myChart2 = new Chart(ctx, {
103
+ type: 'bar',
104
+ data: <%=raw @goal_count_data.to_json %>,
105
+ options: opt
106
+ });
107
+ form.render();
108
+ form.on("submit(search_sales)", function(data) {
109
+ var layer_index = layer.load(0, {shade: [0.1, '#fff']});
110
+ Rails.ajax({
111
+ url: '/missions/sale_trends/sales_analysis',
112
+ type: 'GET',
113
+ data: $.param(data.field, true),
114
+ dataType: "script",
115
+ success: function (res) {
116
+ layer.close(layer_index);
117
+ }
118
+ });
119
+ })
120
+ })
121
+ </script>
@@ -0,0 +1,106 @@
1
+ <div class=" layui-show" style="padding-left: 20px" id="tab_5">
2
+ <div style="margin: 10px 10px 10px 10px">
3
+ <form class="layui-form layui-form-pane" lay-filter="search_form">
4
+ <div class="layui-form-item">
5
+ <div class="layui-inline m-t-10">
6
+ <label class="layui-form-label">维度</label>
7
+ <div class="layui-input-inline">
8
+ <%= select_tag "customer_count_type", options_for_select([['按跟进客户数统计','money'],['按跟进客户次数统计','count']],params[:customer_count_type]), {'lay-filter': 'goal_count'}%>
9
+ </div>
10
+ </div>
11
+ <div class="layui-inline m-t-10">
12
+ <label class="layui-form-label">时间范围</label>
13
+ <div class="layui-input-inline">
14
+ <% options = [['上周','last_week'],['本周','this_week'],['上月','last_month'],['本月','this_month'],['去年','last_year'],['今年','this_year'],['全部','all'],['自定义','diy']]%>
15
+ <%= select_tag "customer_time_range", options_for_select(options, params[:customer_time_range] || 'this_year'), { 'lay-filter': 'customer_time_range'} %>
16
+ </div>
17
+ </div>
18
+ <div class="layui-inline m-t-10 layui-hide diy_range" id="diy_range">
19
+ <label class="layui-form-label">自定义范围</label>
20
+ <div class="layui-input-inline">
21
+ <input type="text" class="layui-input" id="customer_date" name="customer_date" placeholder=" - " autocomplete="off">
22
+ </div>
23
+ </div>
24
+ <div class="layui-inline">
25
+ <button type="button" id="search_bt" class="customer_bt layui-btn layui-btn-primary" lay-submit lay-filter="customer_chart">确定
26
+ </button>
27
+ </div>
28
+ </div>
29
+ </form>
30
+ </div>
31
+ <canvas id="myChart4" width="960" height="200"></canvas>
32
+ </div>
33
+ <script>
34
+ var customer_time_range = "<%= params[:customer_time_range] %>";
35
+ layui.use(['form', 'jquery', 'request', 'element'], function () {
36
+ form = layui.form;
37
+
38
+ var opt = {
39
+ events: false,
40
+ tooltips: {
41
+ enabled: false
42
+ },
43
+ hover: {
44
+ animationDuration: 0
45
+ },
46
+ animation: {
47
+ duration: 1,
48
+ onComplete: function () {
49
+ var chartInstance = this.chart,
50
+ ctx = chartInstance.ctx;
51
+ ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
52
+ ctx.textAlign = 'center';
53
+ ctx.textBaseline = 'bottom';
54
+
55
+ this.data.datasets.forEach(function (dataset, i) {
56
+ var meta = chartInstance.controller.getDatasetMeta(i);
57
+ meta.data.forEach(function (bar, index) {
58
+ var data = dataset.data[index];
59
+ ctx.fillText(data, bar._model.x, bar._model.y - 5);
60
+ });
61
+ });
62
+ }
63
+ }
64
+ };
65
+
66
+ if (customer_time_range == 'diy') {
67
+ $("#diy_range").removeClass('layui-hide');
68
+ }
69
+
70
+ laydate.render({
71
+ elem: '#customer_date',
72
+ range: true,
73
+ value: "<%= params[:customer_date] %>"
74
+ });
75
+
76
+ form.on('select(customer_time_range)', function(data){
77
+ const value = data.value;
78
+ if(value == "diy"){
79
+ $(".diy_range").removeClass("layui-hide")
80
+ }else {
81
+ $(".diy_range").addClass('layui-hide')
82
+ }
83
+ })
84
+
85
+ var ctx = document.getElementById('myChart4');
86
+ var myChart4 = new Chart(ctx, {
87
+ type: 'bar',
88
+ data: <%=raw @customer_data.to_json %>,
89
+ options: opt
90
+ });
91
+
92
+ form.render();
93
+ form.on("submit(customer_chart)", function(data) {
94
+ var layer_index = layer.load(0, {shade: [0.1, '#fff']});
95
+ Rails.ajax({
96
+ url: '/missions/sale_trends/sales_followup_analysis',
97
+ type: 'GET',
98
+ data: $.param(data.field, true),
99
+ dataType: "script",
100
+ success: function (res) {
101
+ layer.close(layer_index);
102
+ }
103
+ });
104
+ })
105
+ })
106
+ </script>
@@ -0,0 +1 @@
1
+ $("#page_4").html("<%= j render 'business_area' %>");
@@ -0,0 +1 @@
1
+ $("#page_2").html("<%= j render 'business_followup_analysis' %>");
@@ -0,0 +1 @@
1
+ $("#page_3").html("<%= j render 'sales_analysis' %>");
@@ -0,0 +1 @@
1
+ $("#page_5").html("<%= j render 'sales_followup_analysis' %>");