diff --git a/app/controllers/attendances_controller.rb b/app/controllers/attendances_controller.rb
new file mode 100644
index 000000000..17eeab1d6
--- /dev/null
+++ b/app/controllers/attendances_controller.rb
@@ -0,0 +1,39 @@
+class AttendancesController < ApplicationController
+ before_action :require_login
+ before_action :find_course, only: [:index, :student_attendances, :history_attendances]
+ before_action :find_attendance, except: [:index, :student_attendances, :history_attendances]
+ before_action :user_course_identity
+
+ def index
+ current_date = Date.current
+ current_end_time = Time.current.strftime("%H:%M:%S")
+
+ if params[:history]
+ @attendances = @course.course_attendances.where("attendance_date < '#{current_date}' or
+ (attendance_date = '#{current_date}' and end_time < '#{current_end_time}')")
+ else
+ @attendances = @course.course_attendances.where("attendance_date > '#{current_date}' or
+ (attendance_date = '#{current_date}' and end_time > '#{current_end_time}')")
+ end
+ @attendances_count = @attendances.size
+
+ @attendances = @attendances.order("attendance_date desc, start_time desc")
+ @attendances = paginate @attendances.includes(:user, :course_member_attendances)
+ end
+
+ def history_attendances
+ current_date = Date.current
+ current_end_time = Time.current.strftime("%H:%M:%S")
+
+ @history_attendances = @course.course_attendances.where("attendance_date < '#{current_date}' or
+ (attendance_date = '#{current_date}' and end_time < '#{current_end_time}')").order("id desc")
+ @all_history_count = @history_attendances.size
+ @history_attendances = paginate @history_attendances.includes(:course_member_attendances)
+ end
+
+ private
+ def find_attendance
+ @attendance = CourseAttendance.find params[:id]
+ @course = @attendance.course
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/watch_video_histories_controller.rb b/app/controllers/watch_video_histories_controller.rb
new file mode 100644
index 000000000..15ee62113
--- /dev/null
+++ b/app/controllers/watch_video_histories_controller.rb
@@ -0,0 +1,10 @@
+class WatchVideoHistoriesController < ApplicationController
+ before_action :require_login
+
+ def create
+ watch_log = CreateWatchVideoService.new(current_user, request, params).call
+ render_ok(log_id: watch_log&.id)
+ rescue CreateWatchVideoService::Error => ex
+ render_error(ex.message)
+ end
+end
diff --git a/app/controllers/weapps/attendances_controller.rb b/app/controllers/weapps/attendances_controller.rb
index 6cc2dda4e..2ec4180a1 100644
--- a/app/controllers/weapps/attendances_controller.rb
+++ b/app/controllers/weapps/attendances_controller.rb
@@ -94,7 +94,9 @@ class Weapps::AttendancesController < ApplicationController
@absence_count = @attendance.absence_count
@all_count = @attendance.course_member_attendances.size
- @_is_current_attendance = @attendance.current_attendance?
+ a_end_time = "#{@attendance.attendance_date} #{@attendance.end_time}".to_time
+
+ @_is_current_attendance = Time.current < a_end_time
if @attendance.course_attendance_groups.first&.course_group_id.to_i == 0
@group_ids = @course.course_groups.pluck(:id) + [0]
diff --git a/app/models/course_video.rb b/app/models/course_video.rb
index 2cfa151ce..e192cf7f8 100644
--- a/app/models/course_video.rb
+++ b/app/models/course_video.rb
@@ -5,4 +5,6 @@ class CourseVideo < ApplicationRecord
validates :title, length: { maximum: 60, too_long: "不能超过60个字符" }, allow_blank: true
validates :link, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }, allow_blank: true
+
+ has_many :watch_course_videos
end
diff --git a/app/models/user.rb b/app/models/user.rb
index b0bd191d2..fb4cc50da 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -162,6 +162,10 @@ class User < ApplicationRecord
has_many :teacher_group_records, dependent: :destroy
+ # 视频观看记录
+ has_many :watch_video_histories, dependent: :destroy
+ has_many :watch_course_video, dependent: :destroy
+
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
diff --git a/app/models/watch_course_video.rb b/app/models/watch_course_video.rb
new file mode 100644
index 000000000..cf2d67028
--- /dev/null
+++ b/app/models/watch_course_video.rb
@@ -0,0 +1,9 @@
+class WatchCourseVideo < ApplicationRecord
+ belongs_to :course_video
+ belongs_to :user
+
+ has_many :watch_video_histories
+
+
+ validates :course_video_id, uniqueness: {scope: :user_id}
+end
diff --git a/app/models/watch_video_history.rb b/app/models/watch_video_history.rb
new file mode 100644
index 000000000..36f7be0f3
--- /dev/null
+++ b/app/models/watch_video_history.rb
@@ -0,0 +1,7 @@
+class WatchVideoHistory < ApplicationRecord
+ belongs_to :user
+ belongs_to :video
+ belongs_to :watch_course_video, optional: true
+
+ validates :duration, numericality: { greater_than_or_equal_to: 0 }
+end
diff --git a/app/services/create_watch_video_service.rb b/app/services/create_watch_video_service.rb
new file mode 100644
index 000000000..8a9bb7f08
--- /dev/null
+++ b/app/services/create_watch_video_service.rb
@@ -0,0 +1,76 @@
+class CreateWatchVideoService < ApplicationService
+ attr_reader :user, :params, :request
+
+ def initialize(user, request, params)
+ @user = user
+ @request = request
+ @params = params
+ end
+
+ def call
+ ActiveRecord::Base.transaction do
+ current_time = Time.now
+ if params[:log_id].present?
+ if params[:total_duration].to_f < params[:watch_duration].to_f || params[:watch_duration].to_f < 0
+ raise Error, '观看时长错误'
+ end
+ # 更新观看时长
+ watch_video_history = user.watch_video_histories.find(params[:log_id])
+
+ if watch_video_history.present? && watch_video_history.watch_duration <= params[:watch_duration].to_f && params[:total_duration].to_f > watch_video_history.total_duration
+ # 如果观看总时长没变,说明视频没有播放,无需再去记录
+
+ watch_video_history.end_at = current_time
+ watch_video_history.total_duration = params[:total_duration]
+ watch_video_history.watch_duration = params[:watch_duration].to_f > watch_video_history.duration ? watch_video_history.duration : params[:watch_duration]
+ watch_video_history.is_finished = (watch_video_history.duration <= params[:watch_duration].to_f)
+ watch_video_history.save!
+
+ watch_course_video = watch_video_history.watch_course_video
+
+ if watch_course_video.present? && !watch_course_video.is_finished && watch_course_video.watch_duration < params[:watch_duration].to_f
+ # 更新课程视频的时长及是否看完状态
+ watch_course_video.watch_duration = params[:watch_duration]
+ watch_course_video.is_finished = (watch_course_video.duration <= params[:watch_duration].to_f)
+ watch_course_video.end_at = current_time
+ watch_course_video.save!
+ end
+ end
+ else
+ # 开始播放时记录一次
+ if params[:course_video_id].present?
+ # 课堂视频
+ course_video = CourseVideo.find(params[:course_video_id])
+ watch_course_video = WatchCourseVideo.find_or_initialize_by(course_video_id: course_video.id, user_id: user.id) do |d|
+ d.start_at = current_time
+ d.duration = params[:duration]
+ end
+
+ watch_video_history = build_video_log(current_time, course_video.video_id, watch_course_video.id)
+ watch_video_history.save!
+
+ watch_course_video.save! unless watch_course_video.persisted?
+ else
+ # 非课堂视频
+ video = Video.find_by(params[:video_id])
+ watch_video_history = build_video_log(current_time, video.id)
+ watch_video_history.save!
+ end
+ end
+ watch_video_history
+ end
+ end
+
+
+ def build_video_log(current_time, video_id, watch_course_video_id=nil)
+ WatchVideoHistory.new(
+ user_id: user.id,
+ watch_course_video_id: watch_course_video_id,
+ start_at: current_time,
+ duration: params[:duration],
+ video_id: video_id,
+ device: params[:device],
+ ip: request.remote_ip
+ )
+ end
+end
\ No newline at end of file
diff --git a/app/views/attendances/index.json.jbuilder b/app/views/attendances/index.json.jbuilder
new file mode 100644
index 000000000..76febf51e
--- /dev/null
+++ b/app/views/attendances/index.json.jbuilder
@@ -0,0 +1,18 @@
+json.attendances @attendances do |attendance|
+ json.(attendance, :id, :name, :normal_count, :all_count, :mode)
+ json.author do
+ user = attendance.user
+ json.user_name user.real_name
+ json.user_login user.login
+ end
+ json.attendance_date attendance.attendance_date.strftime("%Y-%m-%d")
+ json.start_time attendance.start_time.strftime("%H:%M")
+ json.end_time attendance.end_time.strftime("%H:%M")
+ json.edit_auth @user_course_identity < Course::PROFESSOR || attendance.user_id == User.current.id
+
+ if @user_course_identity < Course::PROFESSOR == Course::STUDENT
+ json.attendance_status student_attendance_status(attendance, User.current)
+ end
+end
+
+json.attendances_count @attendances_count
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index db9b74bd2..0f1bc5ba4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -29,6 +29,8 @@ Rails.application.routes.draw do
put 'commons/unhidden', to: 'commons#unhidden'
delete 'commons/delete', to: 'commons#delete'
+ resources :watch_video_histories, only: [:create]
+
resources :jupyters do
collection do
get :save_with_tpi
@@ -548,6 +550,10 @@ Rails.application.routes.draw do
end
end
+ resources :attendances, shallow: true do
+
+ end
+
resources :polls, only:[:index,:new,:create] do
collection do
post :publish # 立即发布
diff --git a/db/migrate/20200305072442_create_watch_course_videos.rb b/db/migrate/20200305072442_create_watch_course_videos.rb
new file mode 100644
index 000000000..5695344fc
--- /dev/null
+++ b/db/migrate/20200305072442_create_watch_course_videos.rb
@@ -0,0 +1,15 @@
+class CreateWatchCourseVideos < ActiveRecord::Migration[5.2]
+ def change
+ create_table :watch_course_videos do |t|
+ t.references :course_video, index: true
+ t.references :user, index: true
+ t.boolean :is_finished, default: false
+ t.float :duration, default: 0
+ t.float :watch_duration, default: 0
+ t.datetime :start_at
+ t.datetime :end_at
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20200305074638_create_watch_video_histories.rb b/db/migrate/20200305074638_create_watch_video_histories.rb
new file mode 100644
index 000000000..ad9645a6a
--- /dev/null
+++ b/db/migrate/20200305074638_create_watch_video_histories.rb
@@ -0,0 +1,18 @@
+class CreateWatchVideoHistories < ActiveRecord::Migration[5.2]
+ def change
+ create_table :watch_video_histories do |t|
+ t.references :watch_course_video, index: true
+ t.references :user, index: true
+ t.references :video, index: true
+ t.boolean :is_finished, default: false
+ t.float :duration, default: 0
+ t.float :watch_duration, default: 0
+ t.datetime :start_at
+ t.datetime :end_at
+ t.string :device
+ t.string :ip
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20200312014100_add_total_duration_to_watch_video_histories.rb b/db/migrate/20200312014100_add_total_duration_to_watch_video_histories.rb
new file mode 100644
index 000000000..e595384e0
--- /dev/null
+++ b/db/migrate/20200312014100_add_total_duration_to_watch_video_histories.rb
@@ -0,0 +1,5 @@
+class AddTotalDurationToWatchVideoHistories < ActiveRecord::Migration[5.2]
+ def change
+ add_column :watch_video_histories, :total_duration, :float, default: 0
+ end
+end
diff --git a/public/react/src/modules/paths/ShixunPathSearch.js b/public/react/src/modules/paths/ShixunPathSearch.js
index be1d3a33c..844215974 100644
--- a/public/react/src/modules/paths/ShixunPathSearch.js
+++ b/public/react/src/modules/paths/ShixunPathSearch.js
@@ -8,7 +8,7 @@ import Pagination from '@icedesign/base/lib/pagination';
import '@icedesign/base/lib/pagination/style.js';
import './ShixunPaths.css';
import KeywordList from '../tpm/shixuns/shixun-keyword-list';
-import btnUrl from '../tpm/shixuns/btn-new.png';
+import btnUrl from './btn-new.png';
class ShixunPathSearch extends Component {
constructor(props) {
@@ -252,7 +252,7 @@ class ShixunPathSearch extends Component {
-