Merge branch 'dev_aliyun' of http://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun

PCqiandao
cxt 5 years ago
commit cdeca5e845

@ -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

@ -5,4 +5,6 @@ class CourseVideo < ApplicationRecord
validates :title, length: { maximum: 60, too_long: "不能超过60个字符" }, allow_blank: true validates :title, length: { maximum: 60, too_long: "不能超过60个字符" }, allow_blank: true
validates :link, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }, allow_blank: true validates :link, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }, allow_blank: true
has_many :watch_course_videos
end end

@ -162,6 +162,10 @@ class User < ApplicationRecord
has_many :teacher_group_records, dependent: :destroy 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 # Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) } scope :active, lambda { where(status: STATUS_ACTIVE) }

@ -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

@ -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

@ -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

@ -29,6 +29,8 @@ Rails.application.routes.draw do
put 'commons/unhidden', to: 'commons#unhidden' put 'commons/unhidden', to: 'commons#unhidden'
delete 'commons/delete', to: 'commons#delete' delete 'commons/delete', to: 'commons#delete'
resources :watch_video_histories, only: [:create]
resources :jupyters do resources :jupyters do
collection do collection do
get :save_with_tpi get :save_with_tpi

@ -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

@ -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

@ -0,0 +1,5 @@
class AddTotalDurationToWatchVideoHistories < ActiveRecord::Migration[5.2]
def change
add_column :watch_video_histories, :total_duration, :float, default: 0
end
end

@ -8,7 +8,7 @@ import Pagination from '@icedesign/base/lib/pagination';
import '@icedesign/base/lib/pagination/style.js'; import '@icedesign/base/lib/pagination/style.js';
import './ShixunPaths.css'; import './ShixunPaths.css';
import KeywordList from '../tpm/shixuns/shixun-keyword-list'; 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 { class ShixunPathSearch extends Component {
constructor(props) { constructor(props) {
@ -252,7 +252,7 @@ class ShixunPathSearch extends Component {
</ul> </ul>
</div> </div>
</div> </div>
<KeywordList btnUrl={btnUrl} onChangeLabel={this.onChangeLabel.bind(this)} OnSearchInput={this.OnSearchInput.bind(this)} onNewHandler={this.getUser.bind(this, '/paths/new')} btnStyle={{ top: '92px' }} /> <KeywordList btnUrl={btnUrl} onChangeLabel={this.onChangeLabel.bind(this)} OnSearchInput={this.OnSearchInput.bind(this)} onNewHandler={this.getUser.bind(this, '/paths/new')} btnStyle={{ top: '72px' }} />
<PathCard {...this.props} {...this.state}></PathCard> <PathCard {...this.props} {...this.state}></PathCard>
{ {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -12,9 +12,11 @@ body {
position: relative; position: relative;
min-height: 100%; min-height: 100%;
} }
body>.-task-title { body>.-task-title {
opacity: 1 !important; opacity: 1 !important;
} }
/*<2A><><EFBFBD><EFBFBD><EFBFBD>Ŵ󾵵<C5B4><F3BEB5B5><EFBFBD><EFBFBD><EFBFBD>·Ŵ󾵵<C5B4>λ<EFBFBD><CEBB>*/ /*<2A><><EFBFBD><EFBFBD><EFBFBD>Ŵ󾵵<C5B4><F3BEB5B5><EFBFBD><EFBFBD><EFBFBD>·Ŵ󾵵<C5B4>λ<EFBFBD><CEBB>*/
#root .search-all { #root .search-all {
width: 219px; width: 219px;
@ -26,26 +28,34 @@ body>.-task-title {
float: left; float: left;
width: 97px; width: 97px;
} }
.head-right i { .head-right i {
font-size: 20px; font-size: 20px;
float: none !important; float: none !important;
} }
.headIcon, #header_keyword_search {
.headIcon,
#header_keyword_search {
padding-top: 13px !important; padding-top: 13px !important;
} }
.search-icon { .search-icon {
height: 30px !important; height: 30px !important;
} }
.search-icon i { .search-icon i {
font-size: 20px; font-size: 20px;
} }
#header_keyword_search i { #header_keyword_search i {
color: #4cacff; color: #4cacff;
} }
.ant-select-selection--multiple{
padding-bottom: 0px!important; .ant-select-selection--multiple {
padding-top:3px; padding-bottom: 0px !important;
padding-top: 3px;
} }
/* 先注释掉下面2个样式这样写影响范围太广了并不是所有的select都需要40px高 */ /* 先注释掉下面2个样式这样写影响范围太广了并不是所有的select都需要40px高 */
/* .ant-select-selection--single{ /* .ant-select-selection--single{
height:40px!important; height:40px!important;
@ -53,30 +63,33 @@ body>.-task-title {
.ant-select-selection__rendered{ .ant-select-selection__rendered{
line-height: 40px!important; line-height: 40px!important;
} */ } */
.ant-select-selection--multiple .ant-select-selection__rendered>ul>li, .ant-select-selection--multiple>ul>li{ .ant-select-selection--multiple .ant-select-selection__rendered>ul>li,
height: 25px!important; .ant-select-selection--multiple>ul>li {
line-height: 23px!important; height: 25px !important;
margin-bottom:3px; line-height: 23px !important;
margin-top:0px; margin-bottom: 3px;
margin-top: 0px;
} }
/*Main START*/ /*Main START*/
.newContainer{ .newContainer {
background: #fafafa!important; background: #fafafa !important;
} }
.ant-modal-title{ .ant-modal-title {
font-size: 16px; font-size: 16px;
font-weight: bold !important; font-weight: bold !important;
color: #333; color: #333;
} }
.ant-modal-title{ .ant-modal-title {
text-align: center; text-align: center;
} }
/*.ant-modal{*/ /*.ant-modal{*/
/*top:10rem !important;*/ /*top:10rem !important;*/
/*}*/ /*}*/
@-moz-document url-prefix() { @-moz-document url-prefix() {
@ -85,24 +98,24 @@ body>.-task-title {
height: 17px !important; height: 17px !important;
} }
} }
/* IE只能用padding不能用上下居中 */ /* IE只能用padding不能用上下居中 */
.shixunDetail_top{ .shixunDetail_top {
display: block!important; display: block !important;
padding-top: 48px; padding-top: 48px;
} }
.totalScore{
display: block!important; .totalScore {
display: block !important;
padding-top: 40px; padding-top: 40px;
} }
.head-nav ul#header-nav li{
/*font-weight: 600;*/
}
/*.newFooter{*/ /*.newFooter{*/
/*position: fixed !important;*/ /*position: fixed !important;*/
/*}*/ /*}*/
.edu-menu-panel .edu-menu-listnew:hover .careersiconfont{ .edu-menu-panel .edu-menu-listnew:hover .careersiconfont {
color: #000 !important; color: #000 !important;
} }
@ -114,16 +127,65 @@ body>.-task-title {
/*-------------------个人主页:右侧提示区域--------------------------*/ /*-------------------个人主页:右侧提示区域--------------------------*/
.-task-sidebar{position:fixed;width:40px;height:180px;right:0;bottom:80px !important;z-index: 10;} .-task-sidebar {
.-task-sidebar>div{height: 40px;line-height: 40px;box-sizing: border-box;width:40px;background:#4CACFF;color:#fff;font-size:20px;text-align:center;margin-bottom:5px;border-radius: 4px;} position: fixed;
.-task-sidebar>div i{ color:#fff;} width: 40px;
.-task-sidebar>div i:hover{color: #fff!important;} height: 180px;
.gotop{background-color: rgba(208,207,207,0.5)!important;padding: 0px!important;} right: 0;
.-task-desc{background:#494949;width:90px;line-height: 36px;text-align: center; bottom: 20px !important;
position: absolute;color: #fff;font-size: 13px;z-index: 999999;opacity: 0;} z-index: 10;
.-task-desc div{position: absolute;top:10px;right: -7px;height: 13px;} }
.-task-desc div img{float: left}
.-task-sidebar .scan_ewm{ .-task-sidebar>div {
height: 40px;
line-height: 40px;
box-sizing: border-box;
width: 40px;
background: #4CACFF;
color: #fff;
font-size: 20px;
text-align: center;
margin-bottom: 5px;
border-radius: 4px;
}
.-task-sidebar>div i {
color: #fff;
}
.-task-sidebar>div i:hover {
color: #fff !important;
}
.gotop {
background-color: rgba(208, 207, 207, 0.5) !important;
padding: 0px !important;
}
.-task-desc {
background: #494949;
width: 90px;
line-height: 36px;
text-align: center;
position: absolute;
color: #fff;
font-size: 13px;
z-index: 999999;
opacity: 0;
}
.-task-desc div {
position: absolute;
top: 10px;
right: -7px;
height: 13px;
}
.-task-desc div img {
float: left
}
.-task-sidebar .scan_ewm {
position: absolute !important; position: absolute !important;
right: 45px !important; right: 45px !important;
bottom: 0px !important; bottom: 0px !important;
@ -135,21 +197,34 @@ body>.-task-title {
display: none; display: none;
height: 213px !important; height: 213px !important;
} }
.trangle_right{position: absolute;right: -5px;bottom: 15px;width: 0;height: 0px;border-top: 6px solid transparent;border-left: 5px solid #494949;border-bottom: 6px solid transparent}
.HeaderSearch{ .trangle_right {
position: absolute;
right: -5px;
bottom: 15px;
width: 0;
height: 0px;
border-top: 6px solid transparent;
border-left: 5px solid #494949;
border-bottom: 6px solid transparent
}
.HeaderSearch {
margin-top: 18px; margin-top: 18px;
margin-right: 20px; margin-right: 20px;
} }
.HeaderSearch .ant-input-search .ant-input{
.HeaderSearch .ant-input-search .ant-input {
/*height:30px;*/ /*height:30px;*/
background: #373e3f !important; background: #373e3f !important;
border: 1px solid #373e3f !important; border: 1px solid #373e3f !important;
} }
.ant-input-search .ant-input-affix-wrapper{
border:transparent; .ant-input-search .ant-input-affix-wrapper {
border: transparent;
} }
.ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled) { .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled) {
/* 比较奇怪的需求先注释掉了如果需要启用麻烦增加class限制别影响别的地方的使用 */ /* 比较奇怪的需求先注释掉了如果需要启用麻烦增加class限制别影响别的地方的使用 */
/* border-color: transparent; */ /* border-color: transparent; */
@ -164,7 +239,7 @@ body>.-task-title {
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
} }
.HeaderSearch .ant-input-search .ant-input::-webkit-input-placeholder{ .HeaderSearch .ant-input-search .ant-input::-webkit-input-placeholder {
color: #999; color: #999;
font-size: 14px; font-size: 14px;
} }
@ -174,12 +249,12 @@ body>.-task-title {
font-size: 14px; font-size: 14px;
} }
.HeaderSearch .ant-input-search .ant-input::-moz-placeholder{ .HeaderSearch .ant-input-search .ant-input::-moz-placeholder {
color: #999; color: #999;
font-size: 14px; font-size: 14px;
} }
.HeaderSearch .ant-input-search .ant-input:-ms-input-placeholder{ .HeaderSearch .ant-input-search .ant-input:-ms-input-placeholder {
color: #999; color: #999;
font-size: 14px; font-size: 14px;
} }
@ -188,112 +263,123 @@ body>.-task-title {
color: #999; color: #999;
} }
.HeaderSearch .ant-input-search .ant-input{ .HeaderSearch .ant-input-search .ant-input {
color: #fff; color: #fff;
} }
.HeaderSearch .ant-input-search .ant-input-suffix{ .HeaderSearch .ant-input-search .ant-input-suffix {
background: transparent !important; background: transparent !important;
} }
.roundedRectangles{ .roundedRectangles {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: -22px; right: -22px;
} }
.HeaderSearch{ .HeaderSearch {
width: 325px; width: 325px;
/*right: 20px;*/ /*right: 20px;*/
} }
.HeaderSearch .ant-input-search{
.HeaderSearch .ant-input-search {
right: 20px; right: 20px;
} }
.mainheighs{
.mainheighs {
height: 100%; height: 100%;
display: block; display: block;
} }
.ml18a{ .ml18a {
margin-left:18%; margin-left: 18%;
} }
.logoimg{ .logoimg {
float: left; float: left;
min-width: 40px; min-width: 40px;
height:40px; height: 40px;
} }
.headwith100b{ .headwith100b {
width: 100%; width: 100%;
} }
.wechatcenter{
.wechatcenter {
text-align: center; text-align: center;
} }
.myrigthsiderbar{ .myrigthsiderbar {
right: 9% !important; right: 9% !important;
} }
.feedbackdivcolor{ .feedbackdivcolor {
background: #33BD8C !important; background: #33BD8C !important;
height: 49px !important; height: 49px !important;
line-height: 24px !important; line-height: 24px !important;
} }
.xiaoshou{
cursor:pointer .xiaoshou {
cursor: pointer
} }
.questiontypes{
width:37px; .questiontypes {
height:17px; width: 37px;
font-size:12px; height: 17px;
color:rgba(51,51,51,1); font-size: 12px;
line-height:17px; color: rgba(51, 51, 51, 1);
cursor:pointer; line-height: 17px;
cursor: pointer;
} }
.questiontype{
.questiontype {
width: 100%; width: 100%;
font-size: 12px; font-size: 12px;
color: #333333; color: #333333;
line-height: 17px; line-height: 17px;
text-align: center; text-align: center;
padding: 11px; padding: 11px;
cursor:pointer; cursor: pointer;
} }
.questiontypeheng{
width:100%; .questiontypeheng {
height:1px; width: 100%;
height: 1px;
background: #EEEEEE; background: #EEEEEE;
} }
.mystask-sidebar{
.mystask-sidebar {
right: 181px !important; right: 181px !important;
} }
.mystask-sidebars{
.mystask-sidebars {
right: 20px !important; right: 20px !important;
} }
.shitikussmys{
width:29px !important; .shitikussmys {
height:20px!important; width: 29px !important;
background:#FF6601 !important; height: 20px !important;
border-radius:10px !important; background: #FF6601 !important;
border-radius: 10px !important;
position: absolute !important; position: absolute !important;
font-size:11px !important; font-size: 11px !important;
color:#ffffff !important; color: #ffffff !important;
line-height:20px !important; line-height: 20px !important;
top: -13px !important; top: -13px !important;
right: -10px !important; right: -10px !important;
} }
.maxnamewidth30{ .maxnamewidth30 {
max-width: 30px; max-width: 30px;
overflow:hidden; overflow: hidden;
text-overflow:ellipsis; text-overflow: ellipsis;
white-space:nowrap; white-space: nowrap;
cursor: default; cursor: default;
} }
.mystask-sidebarss{
.mystask-sidebarss {
right: 5px !important; right: 5px !important;
} }

@ -392,7 +392,7 @@ class ShixunsIndex extends Component {
// console.log(this.state.updata) // console.log(this.state.updata)
return ( return (
<div className="newMain clearfix backFAFAFA"> <div className="newMain clearfix backFAFAFA shi-xun-index">
{this.state.updata === undefined ? "" : <UpgradeModals {this.state.updata === undefined ? "" : <UpgradeModals
{...this.state} {...this.state}
/>} />}

@ -1,3 +1,7 @@
.shi-xun-index .search-keyword-container {
padding: 20px 0 15px 0;
}
.search-keyword-container { .search-keyword-container {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;

Loading…
Cancel
Save