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 :link, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }, allow_blank: true
has_many :watch_course_videos
end

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

@ -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'
delete 'commons/delete', to: 'commons#delete'
resources :watch_video_histories, only: [:create]
resources :jupyters do
collection do
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 './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 {
</ul>
</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>
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

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

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

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

Loading…
Cancel
Save