Merge branch 'dev_aliyun' into develop

Adjustreact
jingquan huang 5 years ago
commit 6695fe753b

2
.gitignore vendored

@ -43,7 +43,7 @@
/public/react/config/stats.json
/public/react/stats.json
/public/react/.idea/*
/public/h5build
/public/npm-debug.log
# avatars

@ -7,17 +7,17 @@ class LiveLinksController < ApplicationController
def index
lives = @course.live_links
order_str = "on_status desc,id desc"
order_str = "on_status desc, live_time desc"
@total_count = lives.size
@my_live_id = @course.live_links.find_by(user_id: current_user.id)&.id
order_str = "live_links.id = #{@my_live_id} desc, #{order_str}" if @my_live_id.present?
# order_str = "live_links.id = #{@my_live_id} desc, #{order_str}" if @my_live_id.present?
lives = lives.order("#{order_str}")
@lives = paginate lives.includes(user: :user_extension)
end
def create
tip_exception("一个老师只能设置一个直播间") if @course.live_links.where(user_id: current_user.id).exists?
@course.live_links.create!(create_params.merge(user_id: current_user.id))
on_status = params[:live_time].present? && params[:live_time].to_time <= Time.now ? 1 : 0
@course.live_links.create!(create_params.merge(user_id: current_user.id, on_status: on_status))
render_ok
end
@ -38,7 +38,8 @@ class LiveLinksController < ApplicationController
end
end
else
current_live.update!(create_params)
on_status = params[:live_time].present? && params[:live_time].to_time <= Time.now ? 1 : 0
current_live.update!(create_params.merge(on_status: on_status))
end
render_ok
end
@ -51,7 +52,7 @@ class LiveLinksController < ApplicationController
private
def create_params
params.permit(:url, :description)
params.permit(:url, :description, :course_name, :platform, :live_time, :duration)
end
def current_live

@ -72,7 +72,7 @@ class SubjectsController < ApplicationController
end
# 排序
order_str = reorder == "publish_time" ? "status = 2 desc, publish_time asc" : "updated_at desc"
order_str = (reorder == "publish_time" ? "homepage_show desc, excellent desc, status = 2 desc, publish_time asc" : "homepage_show desc, excellent desc, updated_at desc")
@subjects = @subjects.reorder(order_str)
end

@ -322,7 +322,7 @@ module TidingDecorator
end
def live_link_content
I18n.t(locale_format) % container&.user.try(:show_real_name)
I18n.t(locale_format) % container&.course_name
end
def student_graduation_topic_content

@ -4,8 +4,12 @@ class LiveLink < ApplicationRecord
has_many :tidings, as: :container, dependent: :destroy
validates :url, presence: true, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }
# validates :url, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }
validates :description, length: { maximum: 100, too_long: "不能超过100个字符" }
validates :course_name, presence: true
validates :platform, presence: true
# validates :live_time, presence: true
validates :duration, numericality: { only_integer: true, greater_than: 0}, allow_blank: true
def op_auth?
user == User.current || User.current.admin_or_business?

@ -25,7 +25,9 @@ class Videos::BatchPublishService < ApplicationService
video.title = param[:title].to_s.strip.presence || video.title
video.apply_publish
if param[:course_id].present?
video.status = "published"
end
video.save!
if param[:course_id].present?

@ -40,10 +40,14 @@ json.exam do
json.all_questions_count @items.size
json.discipline do
json.(@exam.sub_discipline&.discipline, :id, :name)
if @exam.sub_discipline&.discipline.present?
json.(@exam.sub_discipline&.discipline, :id, :name)
end
end
json.sub_discipline do
json.(@exam.sub_discipline, :id, :name)
if @exam.sub_discipline.present?
json.(@exam.sub_discipline, :id, :name)
end
end
json.tag_disciplines @exam.tag_disciplines do |tag|
json.(tag, :id, :name)

@ -17,7 +17,10 @@ json.homeworks @homework_commons.each do |homework|
json.status_time curr_status[:time]
json.time_status curr_status[:time_status]
json.allow_late homework.allow_late
json.author homework.user.real_name
json.author homework.user&.real_name
json.author_img url_to_avatar(homework.user)
json.author_login homework.user&.login
json.created_at homework.created_at.strftime("%Y-%m-%d")
# 只有在主目录才显示
json.upper_category_name homework.course_second_category&.name unless params[:category]

@ -84,6 +84,7 @@ elsif @user_course_identity == Course::STUDENT
json.user_login @work.user.login
json.student_id @work.user.student_id
json.user_name @work.user.real_name
json.user_img url_to_avatar(@work.user)
json.group_name @member.course_group_name
end
@ -108,6 +109,7 @@ if @homework.homework_type == "practice"
json.view_answer_count work.myshixun.try(:view_answer_count).to_i
json.user_login work.user.try(:login)
json.user_name work.user.try(:real_name)
json.user_img url_to_avatar(work.user)
json.student_id work.user.try(:student_id)
json.group_name @students.select{|student| student.user_id == work.user_id}.first.try(:course_group_name)
json.work_status work.compelete_status
@ -169,6 +171,7 @@ elsif @homework.homework_type == "group" || @homework.homework_type == "normal"
json.user_login @is_evaluation ? "--" : work.user.try(:login)
json.user_name @is_evaluation ? "匿名" : work.user.try(:real_name)
json.user_img url_to_avatar(@is_evaluation ? "0" : work.user)
end
end

@ -1 +1 @@
json.(@live, :id, :description, :url)
json.(@live, :id, :description, :url, :platform, :live_time, :duration, :course_name)

@ -1,12 +1,12 @@
json.lives @lives do |live|
json.(live, :id, :description, :on_status)
json.(live, :id, :description, :on_status, :duration, :course_name, :platform)
json.url live.on_status ? live.url : ""
json.author_name live.user.show_real_name
json.author_login live.user.login
json.author_img url_to_avatar(live.user)
json.op_auth live.op_auth?
json.delete_auth live.delete_auth?
json.created_at live.created_at.strftime('%Y-%m-%d')
json.live_time live.live_time&.strftime('%Y-%m-%d %H:%M:%S')
end
json.my_live_id @my_live_id
json.total_count @total_count

@ -238,4 +238,4 @@
2_end: "你提交的发布视频申请:%s审核未通过<br/><span>原因:%{reason}</span>"
PublicCourseStart_end: "你报名参与的开放课程:%s将于%s正式开课"
SubjectStartCourse_end: "您创建的开放课程:%s 已达到开课人数要求。您可以在24小时内自主开设新一期课程。如果超过24小时未开课平台将自动开课并复制您上一期的课程内容。"
LiveLink_end: "%s老师正在直播中"
LiveLink_end: "%s 直播将于30分钟后开始"

@ -173,7 +173,10 @@ zh-CN:
live_link:
description: '说明'
url: '链接'
course_name: '课程名称'
platform: '直播平台'
live_time: '开播时间'
duration: '直播时长'

@ -0,0 +1,8 @@
class AddColumnsToLiveLinks < ActiveRecord::Migration[5.2]
def change
add_column :live_links, :course_name, :string
add_column :live_links, :platform, :string
add_column :live_links, :live_time, :datetime
add_column :live_links, :duration, :integer
end
end

@ -0,0 +1,11 @@
class MigrateUserLocation < ActiveRecord::Migration[5.2]
def change
UserExtension.where("location like '%省'").each do |ue|
ue.update_column("location", ue.location.chop)
end
UserExtension.where("location_city like '%市'").each do |ue|
ue.update_column("location_city", ue.location_city.chop)
end
end
end

@ -0,0 +1,13 @@
namespace :live_notice do
desc "send a live message to students before 30 minutes"
task message: :environment do
lives = LiveLink.where(on_status: 0).where("live_time <= '#{Time.now + 30*60}' and live_time > '#{Time.now}'")
lives.each do |live|
LivePublishJob.perform_later(live.id)
end
end
task on_status: :environment do
LiveLink.where(on_status: 0).where("live_time <= '#{Time.now}'").update_all(on_status: 1)
end
end

@ -1,25 +1,17 @@
import React,{ Component } from "react";
import { Switch } from 'antd';
import { getImageUrl } from 'educoder';
import { Modal } from 'antd';
import { WordsBtn } from 'educoder';
import axios from 'axios';
class LiveItem extends Component{
changeStatus=(flag,event,id)=>{
const url = `/live_links/${id}.json`;
axios.put(url,{
on_status:flag?1:0
}).then(result=>{
if(result){
this.props.showNotification(`直播已${flag?"开启":"关闭"}!`);
const { successFunc } = this.props;
successFunc && successFunc(1);
}
}).catch(error=>{
console.log(error);
})
constructor(props) {
super(props);
this.state = {
visible:false
}
}
deleteLive=(id)=>{
this.props.confirm({
content: '是否确认删除?',
@ -40,39 +32,87 @@ class LiveItem extends Component{
console.log('Cancel');
},
});
}
alertInfo=()=>{
console.log("dddd");
this.setState({
visible:true
})
}
onDialogOkBtnClick=()=>{
this.setState({
visible:false,
})
}
render(){
const { key, item , setLiveId } = this.props;
// let flag = false;
// flag = item.on_status;
const { visible } = this.state;
return(
<div className="liveItem" key={key}>
<Modal
title="提示"
visible={visible}
closable={false}
footer={null}
keyboard={false}
centered={true}
>
<div className="task-popup-content">
<p className="task-popup-text-center font-16 pb20">直播链接失效</p >
</div>
<div className="task-popup-submit clearfix edu-txt-center">
<a className="task-btn task-btn-orange mr51" onClick={this.onDialogOkBtnClick}>知道了</a >
</div>
</Modal>
{
visible ?
<style>{
`
body{
width: calc(100% - 7px)!important;
overflow: hidden!important;
}
.-task-sidebar{
right:42px!important
}
`}
</style>
:
""
}
<div className="lineMiddle livesMain">
<span className="lineMiddle">
<img alt={`${item.author_name}`} className="liveAuthor" src={getImageUrl(`images/${item.author_img}`)}/>
<label>{item.author_name}</label>
<span className="font-18 task-hide" style={{maxWidth:"759px"}}>{item.course_name}</span>
<span className={item.on_status?"labels living":"labels lived"}>{item.on_status?'已开播':'未开播'}</span>
</span>
{
item.op_auth ?
<Switch checkedChildren="on" key={key} className="switchStyle" unCheckedChildren="off" checked={item.on_status} onChange={(flag,event)=>this.changeStatus(flag,event,item.id)}></Switch>:""
}
</div>
<div className="lineMiddle mt15">
<div className="liveDesc">
<p><span className="task-hide-2 break_word">{item.description}</span></p>
</div>
{
item.on_status?
<a className="btns going" target="_blank" href={`${item.url}`}>进入</a>
item.on_status ?
<React.Fragment>
{
item.url ?
<a className="btns going" target="_blank" href={`${item.url}`}>进入</a>
:
<a className="btns going" onClick={this.alertInfo}>进入</a>
}
</React.Fragment>
:
<span className="btns ect">进入</span>
}
</div>
<p className="lineMiddle livesMain mt15 font-12">
<span className="color-grey-9">创建时间{item.created_at}</span>
<span className="lineMiddle color-grey-9">
<img alt={`${item.author_name}`} className="liveAuthor" src={getImageUrl(`images/${item.author_img}`)}/>
<label className="mr50">{item.author_name}</label>
{ item.platform && <span className="mr50">直播平台{item.platform}</span> }
{ item.live_time && <span className="mr50">开播时间{item.live_time}</span>}
{ item.duration && <span className="mr50">直播时长{item.duration}</span> }
</span>
<span>
{
item.op_auth ?
@ -80,7 +120,7 @@ class LiveItem extends Component{
}
{
item.delete_auth ?
<WordsBtn style="s" className="ml30" onClick={()=>this.deleteLive(item.id)}>删除</WordsBtn>
<WordsBtn style="grey" className="ml30" onClick={()=>this.deleteLive(item.id)}>删除</WordsBtn>
:""
}
</span>

@ -1,19 +1,48 @@
import React,{ Component } from "react";
import { Modal , Form , Input , Spin } from 'antd';
import { Modal , Form , Input , Spin , Select , AutoComplete , DatePicker , InputNumber } from 'antd';
import locale from 'antd/lib/date-picker/locale/zh_CN';
import moment from 'moment';
import { handleDateString } from 'educoder';
import './video.css';
import axios from 'axios';
const { TextArea } = Input;
const { Option } = Select;
// ,'威佰通'
const array=['腾讯课堂','B站','斗鱼'];
function range(start, end) {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
}
function disabledDateTime() {
return {
disabledMinutes: () => range(1, 30).concat(range(31, 60)),
};
}
function disabledDate(current) {
return current && current < moment().endOf('day').subtract(1, 'days');
}
class LiveNew extends Component{
constructor(props){
super(props);
this.state={
isSpining:false
isSpining:false,
beginTime:undefined,
}
}
componentDidMount=()=>{
this.checkType();
}
componentDidUpdate=(prevState)=>{
if(prevState && prevState.liveId !== this.props.liveId){
this.setState({
@ -25,7 +54,7 @@ class LiveNew extends Component{
checkType=()=>{
const { liveId } = this.props;
this.clearAll();
if(liveId){
const url =`/live_links/${liveId}/edit.json`;
axios.get(url).then(result=>{
@ -33,14 +62,15 @@ class LiveNew extends Component{
this.props.form.setFieldsValue({
url:result.data.url,
description:result.data.description,
platform:result.data.platform || "腾讯课堂",
duration:result.data.duration,
course_name:result.data.course_name
})
this.setState({
beginTime:result.data.live_time && moment(result.data.live_time,"YYYY-MM-DD HH:mm")
})
}
})
}else{
this.props.form.setFieldsValue({
url:undefined,
description:undefined
})
}
this.setState({
isSpining:false
@ -50,7 +80,9 @@ class LiveNew extends Component{
handleSubmit=()=>{
this.props.form.validateFields((err, values) => {
if(!err){
console.log("2")
this.setState({
isSpining:true
})
const { liveId } = this.props;
if(liveId){
// 修改
@ -64,15 +96,23 @@ class LiveNew extends Component{
// 修改
updateFunc=(id,values)=>{
const url = `/live_links/${id}.json`;
const { beginTime } = this.state;
axios.put(url,{
...values
...values,
live_time:beginTime
}).then(result=>{
if(result){
this.props.showNotification("修改成功!");
const { setliveVisibel } = this.props;
setliveVisibel && setliveVisibel(false,true);
}
this.setState({
isSpining:false
})
}).catch(error=>{
this.setState({
isSpining:false
})
console.log(error);
})
}
@ -81,15 +121,23 @@ class LiveNew extends Component{
creatFunc=(values)=>{
const CourseId=this.props.match.params.coursesId;
const url = `/courses/${CourseId}/live_links.json`;
const { beginTime } = this.state;
axios.post(url,{
...values
...values,
live_time:beginTime
}).then(result=>{
if(result){
this.props.showNotification("添加成功!");
const { setliveVisibel } = this.props;
setliveVisibel && setliveVisibel(false,true);
}
this.setState({
isSpining:false
})
}).catch(error=>{
this.setState({
isSpining:false
})
console.log(error);
})
}
@ -107,23 +155,44 @@ class LiveNew extends Component{
cancelNew=()=>{
const { setliveVisibel } = this.props;
this.clearAll();
setliveVisibel && setliveVisibel(false);
}
clearAll=()=>{
this.props.form.setFieldsValue({
course_name:undefined,
platform:"腾讯课堂",
url:undefined,
description:undefined
description:undefined,
duration:undefined
})
this.setState({
beginTime:undefined
})
}
onChangeTime=(data,dateString)=>{
this.setState({
beginTime:handleDateString(dateString)
})
setliveVisibel && setliveVisibel(false);
}
render(){
const { isSpining } = this.state;
const { isSpining , beginTime } = this.state;
const {getFieldDecorator} = this.props.form;
const { visible } = this.props;
const dataSource = array.map((item,key)=>{
return(
<Option value={item}>{item}</Option>
)
})
return(
<Modal
visible={visible}
width="560px"
title={'直播设置'}
title={'添加直播'}
footer={null}
closable={false}
className="liveModal"
@ -131,13 +200,58 @@ class LiveNew extends Component{
<Spin spinning={isSpining}>
<div className="task-popup-content">
<Form onSubmit={this.handleSubmit}>
<Form.Item label={`直播课程`}>
{getFieldDecorator('course_name', {
rules: [{required: true, message: "请输入课程名称"}],
})(
<Input placeholder="请输入课程名称" />
)}
</Form.Item>
<Form.Item label={`直播平台`}>
{getFieldDecorator('platform', {
rules: [{required: true, message: "请选择直播平台"}],
})(
<AutoComplete
placeholder="请选择或输入直播平台名称"
dataSource={dataSource}
>
</AutoComplete>
)}
</Form.Item>
<Form.Item label={`直播链接`}>
{getFieldDecorator('url', {
rules: [{required: true, message: "请输入第三方直播链接"}],
rules: [],
})(
<Input placeholder="请输入第三方直播链接,如:腾讯课堂播放链接等。" />
)}
</Form.Item>
<div className="flex-bottom">
<div className="flex1">
<p className="ant-col ant-form-item-label color-grey-3 font-16 setStyle">开播时间</p>
<DatePicker
dropdownClassName="hideDisable"
className="timeStyle mb20"
placeholder="如2020/02/02 12:00"
style={{width:"220px"}}
showTime={{ format: 'HH:mm' }}
locale={locale}
format="YYYY-MM-DD HH:mm"
showToday={false}
disabledTime={disabledDateTime}
disabledDate={disabledDate}
value={beginTime && moment(beginTime,"YYYY-MM-DD HH:mm")}
onChange={this.onChangeTime}
></DatePicker>
</div>
<Form.Item label={`直播时长`}>
{getFieldDecorator('duration', {
rules: [],
})(
<InputNumber placeholder="请输入直播时长" style={{width:"220px"}}/>
)}
</Form.Item>
<span className="mb20 ml5">分钟</span>
</div>
<Form.Item label={`直播说明`}>
{getFieldDecorator('description', {
rules: [{
@ -147,10 +261,7 @@ class LiveNew extends Component{
<TextArea rows={4} placeholder="可在此介绍开播具体事项,如开播时间安排等。" />
)}
</Form.Item>
{/* <p className="flex-middle" style={{justifyContent:"space-between"}}>
<span>EduCoder推荐您使用<a href="https://ke.qq.com/" target="_blank" className="color-blue">腾讯课堂</a></span>
<a href="https://pub.idqqimg.com/pc/misc/files/20200204/2e4cb765bef54f0c919c0ab8ab79d969.pdf" target="_blank" className="color-blue">操作指引</a>
</p> */}
</Form>
<div className="clearfix mt30 edu-txt-center">
<a onClick={this.cancelNew} className="task-btn mr30">取消</a>

@ -28,7 +28,6 @@ class VideoIndex extends Component{
lives:undefined,
liveData:undefined,
my_liveId:undefined,
liveId:undefined,
liveVisible:false
@ -48,8 +47,17 @@ class VideoIndex extends Component{
}
componentDidMount=()=>{
const { type ,page } = this.state;
this.checkType(type,page);
const { search } = this.props.location;
const { page } = this.state;
if(search && search === "?open=live"){
this.setState({
type:"live"
})
this.checkType("live",page);
}else{
this.checkType("video",page);
}
}
// 获取直播列表
getLiveList=(page)=>{
@ -66,8 +74,6 @@ class VideoIndex extends Component{
liveData:result.data,
lives:result.data.lives,
isSpining:false,
my_liveId:result.data.my_live_id,
liveId:result.data.my_live_id
})
}
}).catch(error=>{
@ -155,9 +161,8 @@ class VideoIndex extends Component{
// }
// 直播设置
liveSetting=()=>{
const { my_liveId } = this.state;
this.setState({
liveId:my_liveId
liveId:undefined
})
this.setliveVisibel(true);
}
@ -166,6 +171,11 @@ class VideoIndex extends Component{
this.setState({
liveVisible:flag
})
if(flag === false){
this.setState({
liveId:undefined
})
}
if(changetypeFlag){
this.checkType("live",1);
}
@ -195,10 +205,22 @@ class VideoIndex extends Component{
<style>{
`
body{
width: 100%!important;
width: calc(100% - 7px)!important;
overflow: hidden!important;
}
`}</style>:""
.-task-sidebar{
right:42px!important
}
`}</style>:
<style>{
`
.-task-sidebar{
right:35px
}
body{
width: 100%!important;
}
`}</style>
}
<div className="edu-back-white" style={{marginBottom:"1px"}}>
@ -223,7 +245,7 @@ class VideoIndex extends Component{
}
</React.Fragment>
:
<WordsBtn style="blue" className="font-16 ml30" onClick={this.liveSetting}>直播设置</WordsBtn>
<WordsBtn style="blue" className="font-16 ml30" onClick={this.liveSetting}>添加直播</WordsBtn>
}
</li>
}

@ -42,6 +42,11 @@
align-items: center;
height: 25px;
}
.flex-bottom{
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.square-main .buttonRow i {
vertical-align: top;
font-size: 16px;
@ -68,8 +73,8 @@
align-items: center;
}
.liveAuthor{
width: 36px;
height: 36px;
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 20px;
}
@ -117,6 +122,16 @@
line-clamp: 2;
-webkit-box-orient: vertical;
}
.timeStyle{
transition: 0.5s;
}
.borderRed{
border:1px solid #f5222d;
border-radius: 4px;
}
.setStyle{
margin-bottom: 5px!important;
}
.switchStyle.ant-switch-checked{
background-color: #25C03B;
}
@ -128,7 +143,7 @@
}
.liveModal .ant-row.ant-form-item{
position: relative;
margin-bottom: 20px;
margin-bottom: 16px;
}
.liveModal .ant-form-explain{
position: absolute;
@ -136,8 +151,8 @@
left: 0px;
}
.liveModal .ant-col.ant-form-item-label{
height: 30px;
line-height: 30px;
height: 22px;
line-height: 22px;
}
.platform{
background: #fff;

@ -399,15 +399,15 @@ class ShixunhomeWorkItem extends Component{
</WordsBtn>:"":"":"":""
}
{ this.props.isAdmin?<a onClick={()=>this.hrefjumpskip("/courses/"+this.props.match.params.coursesId+"/"+this.state.shixuntypes+"/"+discussMessage.homework_id+"/list?tab=0")} className="btn colorblue font-16 fontweight400 mr20 fr">查看详情</a>:""}
{ this.props.isAdmin?<a onClick={()=>this.hrefjumpskip("/courses/"+this.props.match.params.coursesId+"/"+this.state.shixuntypes+"/"+discussMessage.homework_id+"/list?tab=0")} className="btn colorblue font-16 fontweight400 mr20 fr">作品详情</a>:""}
{
this.props.isStudent? <a onClick={()=>this.hrefjumpskip("/courses/"+this.props.match.params.coursesId+"/"+this.state.shixuntypes+"/"+discussMessage.homework_id+"/list?tab=0")} className="btn colorblue font-16 fontweight400 mr20 fr mt2">查看详情</a>:""
this.props.isStudent? <a onClick={()=>this.hrefjumpskip("/courses/"+this.props.match.params.coursesId+"/"+this.state.shixuntypes+"/"+discussMessage.homework_id+"/list?tab=0")} className="btn colorblue font-16 fontweight400 mr20 fr mt2">作品详情</a>:""
}
{
this.props.isNotMember===true? this.props.discussMessage.private_icon===true?""
:<a onClick={()=>this.hrefjumpskip("/courses/"+this.props.match.params.coursesId+"/"+this.state.shixuntypes+"/"+discussMessage.homework_id+"/list?tab=0")} className="btn colorblue font-16 fontweight400 mr20 fr">查看详情</a>:""
:<a onClick={()=>this.hrefjumpskip("/courses/"+this.props.match.params.coursesId+"/"+this.state.shixuntypes+"/"+discussMessage.homework_id+"/list?tab=0")} className="btn colorblue font-16 fontweight400 mr20 fr">作品详情</a>:""
}

@ -510,6 +510,8 @@ class MessagSub extends Component {
//分组作业
return window.open(`/courses/${item.belong_container_id}/group_homeworks/${item.parent_container_id}`);
}
case 'LiveLink':
return window.open(`/courses/${item.belong_container_id}/course_videos?open=live`);
case 'Hack':
if (item.extra && item.parent_container_type !== 'HackDelete') {
return window.open(`/problems/${item.extra}/edit`);

Loading…
Cancel
Save