Commit d3a588c3 by zhanghaozhe

Merge branch 'limit-free'

# Conflicts:
#	src/components/video/index.js
parents 9462f7d6 16faa68d
No preview for this file type
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
No preview for this file type
No preview for this file type
No preview for this file type
.v-list-base-item {
height: 130px;
//height: 130px;
padding: 10px 10px 0;
position: relative;
......
......@@ -12,6 +12,8 @@ import { Toast } from 'antd-mobile'
import { connect } from "react-redux";
import TopSwiper from './TopSwiper'
import ExpandActiveToast from './expandActiveToast'
import CourseBase from '@/common/course-base'
import { switchTab } from "@components/study/myCourses/actions"
// const animateTypes = Swiper.animateTypes
......@@ -55,7 +57,7 @@ class Index extends Component {
'name': '社区',
'href': 'https://ask.julyedu.com'
}
]
],
}
}
......@@ -68,10 +70,11 @@ class Index extends Component {
http.get(`${API.home}/m/home`).then((res) => {
if (res.data.code === 200) {
const {data} = res.data || {}
const modules = Array.isArray(data.modules) ? data.modules : []
this.setState({
banner: data.banner,
lives: data.lives,
modules: typeof data.modules === 'object' && data.modules.length > 0 ? data.modules : []
modules
})
} else {
Toast.info(res.data.msg, 2)
......@@ -110,11 +113,9 @@ class Index extends Component {
}
toCourseDetail = (id) => {
const {dispatch, history} = this.props;
// dispatch(getCourses(id, () => {
const {history} = this.props;
history.push(`/detail?id=${id}`);
return false;
// }));
}
render() {
......@@ -215,81 +216,57 @@ class Index extends Component {
}
// function TopSwiper({bannerList}) {
// return (
// <Swiper
// type={animateTypes.CARD}
// loop={true}
// height={168}
// autoPlay={true}
// typePro
// createStyle={createStyle}
// >
// {bannerList && bannerList.length > 0 && bannerList.map((item, index) => {
// return (
// Number.isNaN(parseInt(item.jump_url)) ?
// <a href={item.jump_url} key={index}>
// {/* <Link to={item.jump_url} key={index}> */}
// <img className="item" src={item.name} alt="" />
// {/* </Link> */}
// </a> :
// <Link
// to={{
// pathname: '/detail',
// search: `?id=${item.jump_url}`
// }}
// key={index}
// >
// <img
// className="item"
// src={item.name}
// alt=""
// />
// </Link>
// )
// })
// }
// </Swiper>
// )
// }
// 课程模块儿公共组件
// 课程数量是奇数第一个课程需要横着展示沾满一行,课程数量是偶数一行显示两个
function CourseList({modules, toDetail}) {
let filterList = []
let isOdd = modules.list.length % 2 === 0
if (modules.name === '限时免费') {
filterList = modules.list
} else {
// 数量为奇数时,第一个课程显示大图(如后台未上传,前台显示小图),课程数量为偶数时,均显示小图
let filterList = ''
if (isOdd) {
filterList = modules.list
} else {
filterList = modules.list[0].course_img === modules.list[0].course_img_small ? modules.list : modules.list.slice(1)
}
}
return (
<div className='category'>
<h2 className="title">{modules.name}</h2>
<h2 className="title">
{modules.name}
{
modules.name === '限时免费' && <span className={'hot'}>hot</span>
}
</h2>
{
modules.show_more === 1 &&
<Link className="more" to='/classify'>更多 ></Link>
}
{
modules.show_more === 2 &&
<Link className="more" to={modules.more_page}>更多 ></Link>
modules.show_more === 2 ?
modules.name === '限时免费'
? <Link className="more" to={'/free'}>更多 ></Link>
: <Link className="more" to={modules.more_page}>更多 ></Link>
: null
}
<LazyLoad offset={50}>
<ul className='index-course-detail'>
{
!isOdd && modules.list[0].course_img !== modules.list[0].course_img_small &&
modules.name !== '限时免费' && !isOdd && modules.list[0].course_img !== modules.list[0].course_img_small &&
<div className="category-vip" onClick={() => toDetail(modules.list[0].course_id)}>
{/* <Link to={`/detail?id=${modules.list[0].course_id}`}> */}
<img src={modules.list[0].course_img} alt=""/>
{/* </Link> */}
</div>
}
{
filterList.map((item, index) => {
const top = (
const top = item.is_limit_free ? null : (
<div>
{item.is_audition === true &&
<span className='audition'><i className={'iconfont iconerji'}></i>试听</span>
......@@ -298,23 +275,11 @@ function CourseList({modules, toDetail}) {
<span className='return_bash'></span>
}
</div>
);
const bottom = (
<div>
{!item.isbuy && <p className="course-price">
<span className="new">¥{item.discounts_price}</span>
<span className="old">¥{item.price}</span>
</p>
}
{item.isbuy &&
<a className="isbuy">已购买</a>
}
</div>
)
const status = (
<div>
const bottom = <Bottom course={item}/>
const status = item.is_limit_free ? null : <div>
{item.is_bargain &&
<p className='course-status'>砍价减{item.bargain_price}</p>
}
......@@ -322,7 +287,6 @@ function CourseList({modules, toDetail}) {
<p className='course-status'>拼团价{item.groupon_price}</p>
}
</div>
)
return (
<Course
key={index}
......@@ -345,6 +309,50 @@ function CourseList({modules, toDetail}) {
)
}
//限时免费
function LimitFree({course}) {
/*
*
* limit_free_status: 0-未领取 1-已领取 2-已过期
*
* */
switch (course.limit_free_status) {
case 0:
return <Link to={`/detail?id=${course.course_id}`}>
<p className={'course-price'}>
<span className={'free'}>免费领取</span>
<span className={'old'}>¥{course.price}</span>
</p>
</Link>
case 1:
return <div className={'isbuy'}>已领取</div>
default:
return <p className="course-price">
<span className="new">¥{course.discounts_price}</span>
<span className="old">¥{course.price}</span>
</p>
}
}
//课程底部
function Bottom({course}) {
if (course.is_buy) {
if (course.is_limit_free && course.limit_free_status === 1) {
return <div class={'isbuy'}>已领取</div>
} else {
return <div className={'isbuy'}>已购买</div>
}
} else {
return course.is_limit_free
? <LimitFree course={course}/>
: <p className="course-price">
<span className="new">¥{course.discounts_price}</span>
<span className="old">¥{course.price}</span>
</p>
}
}
//近期直播
function ScrollBox(props) {
return (
......@@ -393,5 +401,4 @@ function ScrollBox(props) {
)
}
export default WithTab(Index);
#chatBtn {
bottom: 60px!important;
bottom: 60px !important;
}
.index-box {
overflow: hidden;
background-color: $bg_fff;
......@@ -283,6 +284,20 @@
font-size: 16px;
color: $color_333;
display: inline-block;
.hot {
display: inline-block;
width: 25px;
height: 14px;
margin-left: 5px;
transform: translateY(-5px);
background: rgba(255, 64, 0, 1);
border-radius: 7px 7px 7px 0;
color: #fff;
text-align: center;
line-height: 14px;
font-size: 12px;
}
}
.more {
......@@ -349,14 +364,17 @@
.isbuy {
display: inline-block;
margin-top: 15px;
width: 61px;
height: 18px;
background-color: $bg_active;
border-radius: 9px;
color: $white;
font-size: 12px;
color: $active;
font-size: 15px;
text-align: center;
line-height: 18px;
font-family: PingFang SC;
font-weight: 400;
}
.free {
color: $red;
font-size: 15px;
}
}
......@@ -511,7 +529,7 @@
text-align: center;
height: 47px;
button,a {
button, a {
width: 60px;
height: 27px;
background-color: $bg_active;
......@@ -524,7 +542,8 @@
margin-left: -30px;
bottom: 10px;
}
a{
a {
width: 90px;
line-height: 27px;
margin-left: -45px;
......@@ -740,9 +759,91 @@
// //.is-visible {
// // background-image: none;
// //}
.limit-free {
padding: 0 15px;
color: #333;
h2 {
display: flex;
align-items: center;
margin: 15px 0;
font-size: 15px;
}
.hot {
display: inline-block;
width: 25px;
height: 14px;
margin-left: 5px;
background: rgba(255, 64, 0, 1);
border-radius: 7px 7px 7px 0;
color: #fff;
text-align: center;
line-height: 14px;
font-size: 12px;
}
ul {
display: flex;
flex-wrap: wrap;
li {
margin-right: 15px;
margin-top: 0;
margin-bottom: 20px;
}
& li:nth-child(2n) {
margin-right: 0;
}
}
.origin-price {
color: #999;
font-size: 12px;
text-decoration: line-through;
}
.bottom {
margin-top: 5px;
span {
margin-right: 5px;
}
span:nth-child(3) {
margin-right: 0;
}
}
$red: #FF2121;
.bottom span:nth-child(1), .bottom button:nth-child(1) {
margin-right: 6px;
}
.current-price, .free {
color: $red;
font-size: 15px;
}
button {
width: 61px;
height: 18px;
background: rgba(0, 153, 255, 1);
border-radius: 9px;
text-align: center;
color: #fff;
font-size: 12px;
-webkit-appearance: none;
outline: 0;
border: 0;
}
}
}
.index-box + .nav-bar + .year19-index{
.index-box + .nav-bar + .year19-index {
display: none;
}
......
......@@ -82,7 +82,7 @@ class Classify extends Component {
// 获取课程接口
getList = () => {
const _this = this
_this.setState((state, props)=>({
_this.setState((state, props) => ({
isLoading: true
}));
http.get(`${API.home}/m/course/list/${getParam('id')}`).then((res) => {
......@@ -180,9 +180,9 @@ class Classify extends Component {
renderTabBar={props => {
return (
<Sticky>
{({ style }) => {
{({style}) => {
return (
<div style={{ ...style, top: `${this.state.top}px`, zIndex: 1 }}>
<div style={{...style, top: `${this.state.top}px`, zIndex: 1}}>
<Tabs.DefaultTabBar {...props} />
</div>
)
......@@ -202,14 +202,16 @@ class Classify extends Component {
</p>
<p className='contact text-overflow-2'>{item.desc}</p>
<div className='des'>
{!item.is_buy && <p className="course-price">
{
item.is_restricted ?
<LimitFree course={item}/>
: item.is_buy
? <span className="isbuy">已购买</span>
: <p className="course-price">
<span className="new">¥{item.price1}</span>
<span className="old">¥{item.price0}</span>
</p>
}
{item.is_buy &&
<span className="isbuy">已购买</span>
}
</div>
</div>
)
......@@ -281,4 +283,37 @@ function ClassCourse(props) {
)
}
function LimitFreeStatus({course}) {
/*
* limit-free-status: 0-未领取 1-已领取 2-已过期
*
* */
switch (course.limit_free_status) {
case 0:
return <div className="limit-free">
<span>限时免费</span>
<span>¥{course.price0}</span>
</div>
case 1:
return <div>已领取</div>
case 2:
return <p className="course-price">
<span className="new">¥{course.price1}</span>
<span className="old">¥{course.price0}</span>
</p>
}
}
function LimitFree({course}) {
if (course.is_buy) {
if (course.limit_free_status === 1) {
return <div className={'isbuy'}>已领取</div>
} else {
return <div className={'isbuy'}>已购买</div>
}
} else {
return <LimitFreeStatus course={course}/>
}
}
export default Classify;
html,body,#root {
html, body, #root {
height: 100%;
}
.class-child {
position: relative;
height: 100%;
.preferential{
.preferential {
width: 100%;
height: 44px;
position: fixed;
......@@ -20,7 +21,7 @@ html,body,#root {
// padding: 88px 12px 0;
position: relative;
.custom-render-bar{
.custom-render-bar {
position: fixed;
top: 43px;
left: 0;
......@@ -51,6 +52,7 @@ html,body,#root {
background-color: rgba(224, 46, 36, 0.6);
}
.return_cash {
position: absolute;
width: 31px;
......@@ -103,14 +105,23 @@ html,body,#root {
.isbuy {
display: inline-block;
width: 61px;
height: 18px;
background-color: $bg_active;
border-radius: 9px;
color: $white;
font-size: 12px;
color: #09f;
font-size: 15px;
text-align: center;
line-height: 18px;
}
.limit-free {
span:first-child {
color: #FF2121;
font-size: 15px;
margin-right: 10px;
}
span:last-child {
color: #999;
font-size: 11px;
text-decoration: line-through;
}
}
}
}
......@@ -120,6 +131,7 @@ html,body,#root {
display: flex;
margin-top: 10px;
margin-bottom: 50px;
ul {
width: 100%;
}
......@@ -136,6 +148,7 @@ html,body,#root {
background-color: $bg_fff;
color: $color_666;
}
.am-tabs-tab-bar-wrap {
padding-right: 20px;
}
......@@ -216,6 +229,7 @@ html,body,#root {
margin-bottom: -5px;
font-size: 12px;
}
.active-label {
display: inline-block;
width: 30.5%;
......@@ -234,8 +248,9 @@ html,body,#root {
}
}
}
.am-tabs-default-bar-top .am-tabs-default-bar-tab::after {
background-color: $bg_fff!important;
background-color: $bg_fff !important;
}
}
......@@ -7,7 +7,7 @@
background-color: $bg_fff;
display: flex;
justify-content: space-between;
box-shadow:0px 0px 5px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
z-index: 3;
.consult {
......@@ -84,18 +84,26 @@
font-size: 12px;
}
}
.sign-up-now {
width: 50%;
color: #fff;
font-size: 16px;
background: linear-gradient(270deg,rgba(255,64,0,1) 0%,rgba(253,119,0,1) 100%);
background: linear-gradient(270deg, rgba(255, 64, 0, 1) 0%, rgba(253, 119, 0, 1) 100%);
}
.learn-now {
background-color: #0099FF;
font-size: 16px;
color: #FFF;
}
.get-course {
width: 280px;
height: 44px;
background: linear-gradient(90deg, rgba(253, 119, 0, 1) 0%, rgba(255, 64, 0, 1) 100%);
}
.btn-m {
width: 50%;
}
......
......@@ -82,6 +82,22 @@
}
}
.limit-free{
span:first-child{
color: #FF2121;
font-size: 15px;
margin-right: 10px;
}
.origin-price{
font-size: 11px;
color: #999;
text-decoration: line-through;
}
}
.time-limit{
color: #FF2121;
}
.isbuy {
display: inline-block;
width: 66px;
......@@ -254,3 +270,50 @@
left: 8px;
}
}
.get-course-popup {
top: 210px !important;
padding-bottom: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
.title {
text-align: center;
margin-bottom: 30px;
img {
width: 30px;
height: 30px;
}
div {
font-size: 14px;
color: #525C65;
}
}
.btns {
border-top: 1px solid #DDD;
display: flex;
height: 40px;
button {
-webkit-appearance: none;
width: 50%;
border: none;
outline: none;
color: #333;
background: #fff;
border-radius: 0 0 5px 5px;
font-size: 15px;
}
& button:first-child {
border-right: 1px solid #DDD;
}
& button:last-child {
color: #09f;
}
}
}
\ No newline at end of file
import React, { Component } from 'react'
import { http } from "@/utils"
import './index.scss'
import { HeaderBar } from "@common/index"
import { WhiteSpace, Toast } from "antd-mobile";
import VList from '@/common/v-list-base'
import { Popup } from "@common/index"
import WithFullSize from "@/HOCs/WithFullSize"
import { connect } from "react-redux";
import { Link } from "react-router-dom";
function showToast(msg) {
Toast.info(msg, 2, null, false)
}
class LimitFree extends Component {
nav
state = {
tab: {},
courses: [],
navItemStyle: {},
tabActiveIndex: 0
}
componentDidMount() {
this.getData()
}
getData = () => {
Promise.all([http.get(`${API.home}/sys/category`), http.get(`${API.home}/sys/course`)])
.then(res => {
const [tab, courses] = res
const {data: tabData, code: tabCode, msg: tabMsg} = tab.data
const {data: coursesData, code: coursesCode, msg: coursesMsg} = courses.data
if (tabCode == 200) {
this.setState({
tab: tabData
})
} else {
showToast(tabMsg)
}
if (coursesCode === 200) {
this.setState({
courses: coursesData
})
} else {
showToast(coursesMsg)
}
})
}
handleClick = id => {
this.props.history.push(`/detail?id=${id}`)
}
changeTab = (e, index) => {
const {tabActiveIndex} = this.state
if (tabActiveIndex !== index) {
this.setState({
tabActiveIndex: index
}
)
}
}
getCourse = (courseId, vCourseId) => {
const {user, history} = this.props
if (user.hasError) {
history.push('/passport')
return
}
http.post(`${API.home}/sys/limitFree/receive`, {
course_id: courseId
})
.then(res => {
const {code, msg} = res.data
if (code === 200) {
const instance = Popup({
className: 'get-course-popup',
closable: false,
clickMaskClose: false,
title: <div>
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/check.png" alt=""/>
<div>课程有效期7天,快去学习吧~</div>
</div>,
content: <div className={'btns'}>
<button onClick={() => {
instance.close()
this.getData()
}}>知道了
</button>
<button onClick={() => {
this.toPlay(vCourseId)
instance.close()
}}>立即学习
</button>
</div>
})
} else {
showToast(msg)
}
})
}
toPlay = id => {
this.props.history.push(`/play/video?id=${id}`)
}
formatTime = seconds => ({
d: Math.floor(seconds / 60 / 60 / 24).toString().padStart(2, '0'),
h: Math.floor(seconds / 60 / 60 % 24).toString().padStart(2, '0'),
m: Math.floor(seconds / 60 % 60).toString().padStart(2, '0')
})
render() {
const {tab, courses, navItemStyle, tabActiveIndex} = this.state
return (
<div className='limit-free'>
<HeaderBar arrow={true} title={'限时免费'}></HeaderBar>
<div className="banner">
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/banner.png" alt=""/>
</div>
<nav>
{/*<div className="prev-cover"></div>*/}
<ul ref={el => this.nav = el}>
{
tab && !!tab.length && tab.map((item, index) => {
return (
<li key={index} className={index === tabActiveIndex ? 'active' : ''} style={navItemStyle}
onClick={e => this.changeTab(e, index)}>
<a href={`#category${item.id}`} target={'_self'}>{item.category_name}</a>
</li>
)
})
}
</ul>
<div className="next-cover"></div>
</nav>
<WhiteSpace/>
<div className="course-list">
<ul>
{
tab && !!tab.length && tab.map(category => {
return (
<li key={category.id} className={'category'}>
<h2 id={`category${category.id}`}>
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/category-icon.png"
alt=""/>
<span>{category.category_name}</span>
</h2>
<ul className={'courses'}>
{
courses && courses.length && courses.map((item, index) => {
if (item.category_id != category.id) {
return null
}
/*
* course_status:
* 0未领取 1已领取未过期 2 已领取已过期 3 正常已购买
* */
let des, bottom
switch (item.course_status) {
case 0:
des = <div className={'learner'}>
<i className='iconfont iconRectangleCopy4'/>
<span>{item.play_times}人学习</span>
</div>
bottom = <div className={'bottom'}>
<span className={'red'}>限时免费</span>
<span className={'origin-price'}>¥{item.price0}</span>
<button onClick={e => {
e.stopPropagation()
this.getCourse(item.course_id, item.v_course_id)
}}>免费领取
</button>
</div>
break
case 1:
const {d, h, m} = this.formatTime(item.course_expire)
des = <div className={'remain-time'}>
<i className={'iconfont iconiconfront-21'}/>
<span>{d}{h}{m}分后过期</span>
</div>
bottom = <div className={'bottom'}>
<span className={'purchased'}>已领取</span>
<StudyButton id={item.course_id}/>
</div>
break
case 2:
des = <div className={'remain-time'}>
<i className={'iconfont iconiconfront-21'}/>
<span>{item.play_times}人学习</span>
</div>
bottom = <div className={'bottom'}>
<span className={'red'}>¥{item.price1}</span>
<span className={'origin-price'}>¥{item.price0}</span>
<Link to={`/detail?id=${item.course_id}`}>立即购买</Link>
</div>
break
case 3:
des = <div className={'learner'}>
<i className='iconfont iconRectangleCopy4'/>
<span>{item.play_times}人学习</span>
</div>
bottom = <div className="bottom">
<span className={'purchased'}>已购买</span>
<StudyButton id={item.course_id}/>
</div>
}
const info = (
<div className='info'>
<div className='title'>{item.course_title}</div>
{des}
{bottom}
</div>
)
return (
<VList img={item.image_name}
handleClick={this.handleClick}
id={item.course_id}
info={info}
key={index}
/>
)
})
}
</ul>
</li>
)
})
}
</ul>
</div>
<div className="no-more">
-没有更多了-
</div>
</div>
);
}
}
function StudyButton({id}) {
return <Link to={`/play/video?id=${id}`}>立即学习</Link>
}
export default connect(
state => ({user: state.user}),
null
)
(WithFullSize(LimitFree))
\ No newline at end of file
.limit-free {
background: #F9F9FB;
min-height: 100%;
.banner {
font-size: 0;
img {
width: 100%;
}
}
nav {
position: sticky;
top: 0;
left: 0;
display: flex;
align-items: center;
height: 39px;
background: #fff;
overflow: hidden;
z-index: 999;
.prev-cover, .next-cover {
position: absolute;
top: 0;
width: 44px;
height: 39px;
pointer-events: none;
}
.prev-cover {
left: 0;
background: linear-gradient(90deg, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
}
.next-cover {
right: 0;
background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
}
ul {
width: 100%;
height: 100%;
padding: 0 18px;
display: flex;
align-items: center;
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
& li:nth-last-child(1) {
margin-right: 0;
}
}
li {
text-align: center;
height: 100%;
line-height: 39px;
flex-shrink: 0;
margin-right: 23px;
&.active {
color: #09f;
border-bottom: 1px solid #09f;
}
}
}
.course-list {
border-top: 1px solid transparent;
}
.category {
padding: 0 12px;
background: #fff;
margin-bottom: 8px;
border-top: 1px solid transparent;
}
h2 {
padding-top: 50px;
margin-top: -50px;
background-clip: content-box;
}
.courses li:nth-last-child(1) {
margin-bottom: 0;
& div {
border-bottom: none;
}
}
.v-list-base-item {
padding: 0;
margin-bottom: 18px;
}
h2 {
display: flex;
align-items: center;
margin: -32px 0 18px;
img {
width: 12px;
height: 12px;
margin-right: 6px;
}
span {
font-size: 16px;
color: #333;
}
}
.iconfont {
font-size: 12px;
margin-right: 4px;
}
.info {
width: 50%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
flex-direction: column;
flex: auto;
font-size: 12px;
color: #999;
.title {
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-family: "NotoSansHans-Medium", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
.learner, .remain-time, {
margin-top: -30px;
}
.red {
color: #FF2121;
font-size: 15px;
margin-right: 10px;
}
.origin-price {
color: #999;
font-size: 12px;
text-decoration: line-through;
}
button, a {
width: 68px;
height: 24px;
float: right;
border-radius: 3px;
background: #09f;
border: none;
font-size: 13px;
color: #fff;
line-height: 24px;
text-align: center;
}
button {
-webkit-appearance: none;
outline: 0;
}
.bottom {
width: 100%;
height: 24px;
align-self: flex-end;
font-size: 0;
line-height: 24px;
span {
font-size: 12px;
}
.purchased {
color: #09f;
}
}
}
.no-more {
width: 375px;
height: 82px;
line-height: 82px;
background: #F7F9FC;
font-size: 14px;
color: #AAA;
text-align: center;
}
}
.get-course-popup {
top: 210px !important;
padding-bottom: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
.title {
text-align: center;
margin-bottom: 30px;
img {
width: 30px;
height: 30px;
}
div {
font-size: 14px;
color: #525C65;
}
}
.btns {
border-top: 1px solid #DDD;
display: flex;
height: 40px;
button {
-webkit-appearance: none;
width: 50%;
border: none;
outline: none;
color: #333;
background: #fff;
border-radius: 0 0 5px 5px;
font-size: 15px;
}
& button:first-child {
border-right: 1px solid #DDD;
}
& button:last-child {
color: #09f;
}
}
}
\ No newline at end of file
import React, {Component} from 'react';
import React, { Component } from 'react';
import './index.scss';
import {HeaderBar, VList} from '../../common'
import { http } from "@/utils";
import {Link} from 'react-router-dom'
import {Toast} from 'antd-mobile'
import {connect} from "react-redux"
import {getCourses} from './../detail/actions';
import { HeaderBar, VList } from '../../common'
import { http, dateCountDown } from "@/utils";
import { Link } from 'react-router-dom'
import { Toast } from 'antd-mobile'
import { connect } from "react-redux"
import Loading from '@/common/Loading'
class Purchased extends Component {
......@@ -36,19 +35,17 @@ class Purchased extends Component {
}
toCourseDetail = (id) => {
const { dispatch, history } = this.props;
const {dispatch, history} = this.props;
// dispatch(getCourses(id, () => {
history.push(`/detail?id=${id}`)
// }));
}
render() {
const {user} = this.props
const uid = user && user.data && user.data.uid
return (
<div className='purchased-box'>
<HeaderBar arrow={true} title='已购课程' cart={false} toHref='/my' />
<HeaderBar arrow={true} title='已购课程' cart={false} toHref='/my'/>
<Loading isLoading={this.state.isLoading}>
{
this.state.data && this.state.data.length > 0 ?
......@@ -59,9 +56,7 @@ class Purchased extends Component {
const Info = (
<div className="info">
<p className='title' onClick={() => this.toCourseDetail(item.course_id)}>
{/* <Link to={`/detail?id=${item.course_id}`}> */}
{item.course_title}
{/* </Link> */}
</p>
<p className='contact text-overflow-2'>{item.simpledescription}</p>
......@@ -83,7 +78,7 @@ class Purchased extends Component {
item.is_aist && <span className='status'>返现</span>
)
const courseExpire = (
item.course_expire && item.course_expire!='' &&
item.course_expire && item.course_expire != '' &&
<span className='course-expire'>{item.course_expire}</span>
)
return (
......
html, body, #root {
height: 100%!important;
height: 100% !important;
}
.purchased-box {
width: 100%;
height: 100%;
......@@ -110,17 +111,18 @@ html, body, #root {
color: #fff;
background: linear-gradient(to bottom, #FF4000, #FD7700);
}
.course-expire{
.course-expire {
display: inline-block;
text-align: center;
position: absolute;
bottom: 10px;
left: 0;
width:92px;
height:20px;
width: 92px;
height: 20px;
line-height: 21px;
background-color: #FF3A3A;
border-radius:0 10px 10px 0;
border-radius: 0 10px 10px 0;
color: #fff;
font-size: 12px;
}
......
......@@ -79,7 +79,7 @@ class MyCourses extends Component {
handleClick = (id, item) => {
const {history} = this.props
const {mode, course_id} = item
if(mode && mode == 6){
if (mode && mode == 6) {
history.push(`/python?id=${course_id}`)
return
}
......@@ -129,15 +129,15 @@ class MyCourses extends Component {
<div className="info">
<div className='title'>{item.course_title}</div>
{
item.is_aist &&
!item.is_restricted && item.is_aist &&
<div className='contact'>助教微信:{item.assist_weixin}</div>
}
{
!item.is_aist && item.contact_type == 1 && item.course_qq &&
!item.is_restricted && !item.is_aist && item.contact_type == 1 && item.course_qq &&
<div className='contact'>QQ群:{item.course_qq}</div>
}
{
!item.is_aist && item.contact_type == 2 && item.course_qq &&
!item.is_restricted && !item.is_aist && item.contact_type == 2 && item.course_qq &&
<div className='contact'>班主任微信:{item.course_qq}</div>
}
......@@ -156,11 +156,11 @@ class MyCourses extends Component {
)
const status = (
item.is_aist && <span className='status'>返现</span>
)
const courseExpire = (
item.course_expire && item.course_expire!='' &&
<span className='course-expire'>{item.course_expire}</span>
item.is_aist
? <span className='status'>返现</span>
: item.course_expire
? <span className='course-expire'>{item.course_expire}</span>
: null
)
return (
<VList img={item.image_name}
......@@ -169,7 +169,6 @@ class MyCourses extends Component {
key={index}
info={Info}
status={status}
courseExpire={courseExpire}
item={item}
id={item['v_course_id']}
/>
......
import React, { Component } from 'react'
import HeaderBar from '@/common/HeaderBar'
import './video.scss'
import { NavLink, Route, Redirect, Switch, Link } from 'react-router-dom'
import { NavLink, Route, Redirect, Switch } from 'react-router-dom'
import { http, getParam, browser } from '@/utils'
import Recommendation from './recommendation'
import VideoCatalog from './video-catalog'
......@@ -103,6 +103,11 @@ class Video extends Component {
singleType: 1,// 单集购买需要
nowPrice: 0,// 单集购买需要
laterPrice: 0,// 单集购买需要
limitFreeNoPromptChecked: false,//是否勾选"不再显示此弹框"选项
showLimitFreePopup: false,
limitFreePopup: {},
isShowNeverShowPopupOption: false, //限时免费课程 播放结束后是否显示"不再显示此弹框"选项
limitFreePopupVideos: JSON.parse(localStorage.getItem('limit-free-popup-videos'))
}
......@@ -149,7 +154,6 @@ class Video extends Component {
}
// 购买单集
toSingleset = (item) => {
// console.log(item);
this.setState({
singleBox: true,
singleType: 1,
......@@ -356,9 +360,8 @@ class Video extends Component {
this.ws.addEventListener('message', e => {
const data = JSON.parse(e.data);
data.code == 4040 && (this.reconnect = false);
if(data.code === 0) {
console.log("上次的学习记录" + JSON.stringify(data));
if(data.data && data.data.position) {
if (data.code === 0) {
if (data.data && data.data.position) {
this.player.currentTime(data.data.position);
}
}
......@@ -367,12 +370,12 @@ class Video extends Component {
sendMessage = message => {
let readyState = this.ws.readyState, _this = this;
if(readyState === 1) {
if (readyState === 1) {
this.ws && this.ws.send(JSON.stringify(message))
}else if(readyState === 3) {
} else if (readyState === 3) {
this.ws.close();
this.ws = null;
let reconnect = setTimeout(function() {
let reconnect = setTimeout(function () {
clearTimeout(reconnect);
reconnect = null;
_this.ws = new WebSocket(PROCESS_URL);
......@@ -395,7 +398,6 @@ class Video extends Component {
countSchedule = () => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
if (Number(course.course_id) === 0 || course.course_id === '') {
console.log('免费课程 拦截');
return;
}
let ctype = 0;
......@@ -420,7 +422,6 @@ class Video extends Component {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
// 免费课程不发送
// if (Number(course.course_id) === 0 || course.course_id === '') {
// console.log('免费课程 拦截');
// return;
// }
// 时间为0 不发送消息
......@@ -433,7 +434,6 @@ class Video extends Component {
}
// 时间足够不发送
// if(this.timeEnough) {
// console.log('5001 时间足够');
// return;
// }
this.sendMessage({
......@@ -489,7 +489,7 @@ class Video extends Component {
this.player.on('play', () => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
// 当视频播放时 看是否是第一次播放(初次进入页面 刷新页面 切换视频 都是第一次播放 需要获取上次的播放时间)
if(this.isCurrentVideoFirstPlay) {
if (this.isCurrentVideoFirstPlay) {
// 当某些原因导致视频暂停时(用户暂停 网络不好等) 再播放时不需要发送
this.isCurrentVideoFirstPlay = false;
// 发送消息 recent_learn
......@@ -504,7 +504,7 @@ class Video extends Component {
is_live: 0,
}))
}
if(!this.timer) {
if (!this.timer) {
this.setupTimer();
}
})
......@@ -519,11 +519,16 @@ class Video extends Component {
this.count = this.watchSec = 0;
this.countSchedule(); // 计算进度 -- 播放完毕
// 返现课程才出现打卡记录
if(this.state.course.is_aist) {
if (this.state.course.is_aist) {
this.getShareProgressInfo()
}
clearInterval(this.timer);
this.timer = null;
if (this.state.limitFreePopup.is_free) {
this.setState({
showLimitFreePopup: true
})
}
})
}
......@@ -552,7 +557,6 @@ class Video extends Component {
if (index === this.state.activeIndex) {
return
}
console.log('selectVideo 先发送时间 再发送进度 在重置定时器');
this.isCurrentVideoFirstPlay = true; // 切换视频则重置这个变量 因为新视频肯定是首次播放
this.sendWatchTime(this.watchSec, this.currentPlaybackRate)
this.countSchedule(); // 计算进度 -- 选择新视频(可能是M端特有的)
......@@ -604,6 +608,7 @@ class Video extends Component {
}),
this.playSetup
)
this.getLimitFreePopup(data.course.course_id)
} else {
Toast.info(data.msg)
}
......@@ -615,7 +620,6 @@ class Video extends Component {
// is_aist,是否AI特训营
const {course = {}} = this.state;
// if (Number(course.course_id) === 0 || course.course_id === '') {
// console.log('免费课程 拦截');
// }else{
let _this = this;
this.setupWS();
......@@ -733,10 +737,60 @@ class Video extends Component {
}
}
getLimitFreePopup = id => {
http.post(`${API.home}/sys/popup`, {
course_id: id
})
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
const {courseId, limitFreePopupVideos} = this.state
this.setState({
limitFreePopup: data,
isShowNeverShowPopupOption: limitFreePopupVideos ? limitFreePopupVideos.includes(courseId) : false
})
} else {
Toast.info(msg, 2, null, false)
}
})
}
checkNeverShowLimitFreePopup = () => {
if (!this.state.limitFreeNoPromptChecked) {
return
}
http.post(`${API.home}/sys/checklist`, {
course_id: this.state.course.course_id
})
.then(res => {
const {code, msg} = res.data
if (code === 200) {
this.setState({
limitFreePopup: {...this.state.limitFreePopup, is_free: 0}
})
} else {
Toast.info(msg, 2, null, false)
}
})
}
render() {
let {match, location, history} = this.props
const {videoList, activeIndex, isAuth, salePrice, course, singleBox, singleType} = this.state;
const {
videoList,
activeIndex,
isAuth,
salePrice,
course,
singleBox,
singleType,
showLimitFreePopup,
limitFreePopup,
isShowNeverShowPopupOption
} = this.state;
let toHref = '';
if (location.state && location.state.to && location.state.to === 'detail') {
toHref = `/detail?id=${course.course_id}`
......@@ -851,7 +905,6 @@ class Video extends Component {
activeIndex={this.state.activeIndex}
selectVideo={this.selectVideo}
videoCatalog={videoList}
isAist={course.is_aist}
{...props}
/>
);
......@@ -869,7 +922,50 @@ class Video extends Component {
closeShareModal={() => this.setState({isShowShareModal: false})}
data={this.state.shareData}
/>
{
showLimitFreePopup &&
<div className={'limit-free-cover'}>
<div className="free-popup">
<div className="title">
<span>{limitFreePopup.pop_descbition}</span>
</div>
<div className={'des'}>
<img className="qrcode"
src={limitFreePopup.wechat_img} alt=''/>
<span>长按/扫码识别</span>
<span>添加时请备注<span>{course.course_id}</span>哦</span>
<div className="no-prompt">
{
isShowNeverShowPopupOption &&
<label htmlFor="no-prompt">
<span
className={`checkbox-label ${this.state.limitFreeNoPromptChecked ? 'checked' : 'unchecked'}`}>
<i className={'iconfont iconiconfront-73'}/>
</span>
<input type="checkbox" id={'no-prompt'} onChange={(e) => {
this.setState({
limitFreeNoPromptChecked: e.target.checked
})
}}/>
<span>本课程不再提示</span>
</label>
}
</div>
</div>
<i className={'close-btn iconfont iconiconfront-2'} onClick={() => {
this.setState({
showLimitFreePopup: false,
isShowNeverShowPopupOption: true
})
const {courseId, limitFreePopupVideos} = this.state
localStorage.setItem('limit-free-popup-videos', JSON.stringify(
limitFreePopupVideos ? [...limitFreePopupVideos, courseId] : [courseId]
))
this.checkNeverShowLimitFreePopup()
}}/>
</div>
</div>
}
</div>
);
}
......
......@@ -20,7 +20,6 @@ $tabHeight: 44px;
background: rgba(0, 0, 0, 0.5);
.vjs-custom-play-button {
position: absolute;
top: 50%;
......@@ -33,8 +32,8 @@ $tabHeight: 44px;
}
}
&.vjs-has-started{
.vjs-custom-play-button-cover{
&.vjs-has-started {
.vjs-custom-play-button-cover {
bottom: 2.9em;
}
}
......@@ -281,3 +280,123 @@ $tabHeight: 44px;
}
}
}
.limit-free-cover {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, .8);
z-index: 999;
}
.free-popup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 290px;
height: 366px;
border-radius: 5px !important;
padding: 0 !important;
background: url("https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/popup-bg.png") !important;
background-size: cover !important;
.title {
display: flex;
align-items: center;
height: 125px;
padding: 0 20px;
color: #fff !important;
font-size: 15px;
}
.des {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 20px;
.qrcode {
margin-bottom: 10px;
width: 118px;
height: 118px;
}
& > span:nth-of-type(1) {
color: #666;
font-size: 13px;
margin-bottom: 10px;
}
& > span:nth-of-type(2) {
color: #333;
font-size: 15px;
margin-bottom: 14px;
}
span span {
color: #FF2121;
}
.no-prompt {
label {
position: relative;
padding-left: 21px;
height: 14px;
}
input, .checkbox-label {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 13px;
height: 13px;
-webkit-appearance: none;
outline: 0;
background: #fff;
}
input {
opacity: 0;
}
.checkbox-label {
border: 1px solid rgba(84, 92, 100, .6);
border-radius: 1px;
left: -1px;
box-sizing: border-box;
.iconfont {
color: #fff;
font-size: 12px;
}
}
span {
color: #545C64;
font-size: 13px;
margin-bottom: 0;
line-height: 14px;
}
.checked {
background: #09f;
}
}
}
.close-btn {
position: absolute;
bottom: -44px;
left: 50%;
transform: translateX(-50%);
font-size: 26px;
color: #fff;
}
}
......@@ -299,4 +299,10 @@ export default [
exact: true,
component: loadable(() => import('@/components/college/courseList'))
},
//限时免费落地页
{
path:'/free',
exact: true,
component: loadable(() => import(/*limit-free*/'@/components/limit-free'))
}
]
import jsCookie from "js-cookie";
import {
differenceInDays,
differenceInHours,
differenceInMinutes,
differenceInSeconds
} from 'date-fns'
export const getParam = (key, str) => {
......@@ -8,7 +14,9 @@ export const getParam = (key, str) => {
return (found = re.exec(_s)) ? found[2] : null;
}
const html = content => ({__html: htmlDecode(content)})
const html = content => ({
__html: htmlDecode(content)
})
const htmlDecode = content => {
let e = document.createElement('div');
......@@ -93,8 +101,38 @@ const isLogin = (function () {
return jsCookie.get('uid') && jsCookie.get('token')
})()
const dateCountDown = (later, earlier) => {
const d = differenceInDays(later, earlier)
const h = differenceInHours(later, earlier) % 24
const m = differenceInMinutes(later, earlier) % 60
const s = differenceInSeconds(later, earlier) % 60
return {
d,
h,
m,
s
}
}
export {default as http} from './http'
export {default as wxShare} from './wechat/share'
export {html, initCaptcha, validateTel, validateEmail, browser, isLogin}
export {default as SendMessageToApp} from './app'
export {
default as http
}
from './http'
export {
default as wxShare
}
from './wechat/share'
export {
html,
initCaptcha,
validateTel,
validateEmail,
browser,
isLogin,
dateCountDown
}
export {
default as SendMessageToApp
}
from './app'
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment