Commit 17563eb1 by zhanghaozhe

Merge branch 'limit-free' into dev

# Conflicts:
#	src/components/video/index.js
parents 16d7e00c 8c943d62
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 { .v-list-base-item {
height: 130px; //height: 130px;
padding: 10px 10px 0; padding: 10px 10px 0;
position: relative; position: relative;
......
...@@ -12,6 +12,7 @@ import { Toast } from 'antd-mobile' ...@@ -12,6 +12,7 @@ import { Toast } from 'antd-mobile'
import { connect } from "react-redux"; import { connect } from "react-redux";
import TopSwiper from './TopSwiper' import TopSwiper from './TopSwiper'
import ExpandActiveToast from './expandActiveToast' import ExpandActiveToast from './expandActiveToast'
import CourseBase from '@/common/course-base'
// const animateTypes = Swiper.animateTypes // const animateTypes = Swiper.animateTypes
...@@ -55,7 +56,7 @@ class Index extends Component { ...@@ -55,7 +56,7 @@ class Index extends Component {
'name': '社区', 'name': '社区',
'href': 'https://ask.julyedu.com' 'href': 'https://ask.julyedu.com'
} }
] ],
} }
} }
...@@ -110,11 +111,9 @@ class Index extends Component { ...@@ -110,11 +111,9 @@ class Index extends Component {
} }
toCourseDetail = (id) => { toCourseDetail = (id) => {
const {dispatch, history} = this.props; const {history} = this.props;
// dispatch(getCourses(id, () => {
history.push(`/detail?id=${id}`); history.push(`/detail?id=${id}`);
return false; return false;
// }));
} }
render() { render() {
...@@ -215,45 +214,6 @@ class Index extends Component { ...@@ -215,45 +214,6 @@ 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}) { function CourseList({modules, toDetail}) {
...@@ -268,28 +228,32 @@ function CourseList({modules, toDetail}) { ...@@ -268,28 +228,32 @@ function CourseList({modules, toDetail}) {
} }
return ( return (
<div className='category'> <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 && modules.show_more === 1 &&
<Link className="more" to='/classify'>更多 ></Link> <Link className="more" to='/classify'>更多 ></Link>
} }
{ {
modules.show_more === 2 && modules.show_more === 2 &&
<Link className="more" to={modules.more_page}>更多 ></Link> <Link className="more" to={modules.name === '限时免费' ? '/free' : modules.more_page}>更多 ></Link>
} }
<LazyLoad offset={50}> <LazyLoad offset={50}>
<ul className='index-course-detail'> <ul className='index-course-detail'>
{ {
!isOdd && modules.list[0].course_img !== modules.list[0].course_img_small && !isOdd && modules.list[0].course_img !== modules.list[0].course_img_small &&
<div className="category-vip" onClick={() => toDetail(modules.list[0].course_id)}> <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=""/> <img src={modules.list[0].course_img} alt=""/>
{/* </Link> */}
</div> </div>
} }
{ {
filterList.map((item, index) => { filterList.map((item, index) => {
const top = ( const top = item.is_limit_free ? null : (
<div> <div>
{item.is_audition === true && {item.is_audition === true &&
<span className='audition'><i className={'iconfont iconerji'}></i>试听</span> <span className='audition'><i className={'iconfont iconerji'}></i>试听</span>
...@@ -298,22 +262,26 @@ function CourseList({modules, toDetail}) { ...@@ -298,22 +262,26 @@ function CourseList({modules, toDetail}) {
<span className='return_bash'></span> <span className='return_bash'></span>
} }
</div> </div>
); )
const bottom = ( const bottom = (
<div> <div>
{!item.isbuy && <p className="course-price"> {item.is_buy
? <a className="isbuy">已购买</a>
: item.is_limit_free
? <p className={'course-price'}>
<span className={'free'}>免费领取</span>
<span className={'old'}>¥{item.price}</span>
</p>
: <p className="course-price">
<span className="new">¥{item.discounts_price}</span> <span className="new">¥{item.discounts_price}</span>
<span className="old">¥{item.price}</span> <span className="old">¥{item.price}</span>
</p> </p>
} }
{item.isbuy &&
<a className="isbuy">已购买</a>
}
</div> </div>
) )
const status = ( const status = item.is_limit_free ? null : (
<div> <div>
{item.is_bargain && {item.is_bargain &&
<p className='course-status'>砍价减{item.bargain_price}</p> <p className='course-status'>砍价减{item.bargain_price}</p>
...@@ -393,5 +361,4 @@ function ScrollBox(props) { ...@@ -393,5 +361,4 @@ function ScrollBox(props) {
) )
} }
export default WithTab(Index); export default WithTab(Index);
#chatBtn { #chatBtn {
bottom: 60px!important; bottom: 60px !important;
} }
.index-box { .index-box {
overflow: hidden; overflow: hidden;
background-color: $bg_fff; background-color: $bg_fff;
...@@ -283,6 +284,20 @@ ...@@ -283,6 +284,20 @@
font-size: 16px; font-size: 16px;
color: $color_333; color: $color_333;
display: inline-block; 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 { .more {
...@@ -358,6 +373,11 @@ ...@@ -358,6 +373,11 @@
text-align: center; text-align: center;
line-height: 18px; line-height: 18px;
} }
.free{
color: $red;
font-size: 15px;
}
} }
/* /*
...@@ -511,7 +531,7 @@ ...@@ -511,7 +531,7 @@
text-align: center; text-align: center;
height: 47px; height: 47px;
button,a { button, a {
width: 60px; width: 60px;
height: 27px; height: 27px;
background-color: $bg_active; background-color: $bg_active;
...@@ -524,7 +544,8 @@ ...@@ -524,7 +544,8 @@
margin-left: -30px; margin-left: -30px;
bottom: 10px; bottom: 10px;
} }
a{
a {
width: 90px; width: 90px;
line-height: 27px; line-height: 27px;
margin-left: -45px; margin-left: -45px;
...@@ -740,9 +761,89 @@ ...@@ -740,9 +761,89 @@
// //.is-visible { // //.is-visible {
// // background-image: none; // // 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; display: none;
} }
......
...@@ -82,7 +82,7 @@ class Classify extends Component { ...@@ -82,7 +82,7 @@ class Classify extends Component {
// 获取课程接口 // 获取课程接口
getList = () => { getList = () => {
const _this = this const _this = this
_this.setState((state, props)=>({ _this.setState((state, props) => ({
isLoading: true isLoading: true
})); }));
http.get(`${API.home}/m/course/list/${getParam('id')}`).then((res) => { http.get(`${API.home}/m/course/list/${getParam('id')}`).then((res) => {
...@@ -180,9 +180,9 @@ class Classify extends Component { ...@@ -180,9 +180,9 @@ class Classify extends Component {
renderTabBar={props => { renderTabBar={props => {
return ( return (
<Sticky> <Sticky>
{({ style }) => { {({style}) => {
return ( return (
<div style={{ ...style, top: `${this.state.top}px`, zIndex: 1 }}> <div style={{...style, top: `${this.state.top}px`, zIndex: 1}}>
<Tabs.DefaultTabBar {...props} /> <Tabs.DefaultTabBar {...props} />
</div> </div>
) )
...@@ -202,13 +202,18 @@ class Classify extends Component { ...@@ -202,13 +202,18 @@ class Classify extends Component {
</p> </p>
<p className='contact text-overflow-2'>{item.desc}</p> <p className='contact text-overflow-2'>{item.desc}</p>
<div className='des'> <div className='des'>
{!item.is_buy && <p className="course-price"> {
!item.is_buy ?
item.is_restricted ?
<div className="limit-free">
<span>限时免费</span>
<span>¥{item.price0}</span>
</div>
: <p className="course-price">
<span className="new">¥{item.price1}</span> <span className="new">¥{item.price1}</span>
<span className="old">¥{item.price0}</span> <span className="old">¥{item.price0}</span>
</p> </p>
} : <span className="isbuy">已购买</span>
{item.is_buy &&
<span className="isbuy">已购买</span>
} }
</div> </div>
</div> </div>
......
...@@ -112,6 +112,19 @@ html,body,#root { ...@@ -112,6 +112,19 @@ html,body,#root {
text-align: center; text-align: center;
line-height: 18px; 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;
}
}
} }
} }
} }
......
...@@ -523,6 +523,19 @@ class BtnStatus extends Component { ...@@ -523,6 +523,19 @@ class BtnStatus extends Component {
</Overlay> </Overlay>
} }
{
//todo 联调
info.is_limit_free && info.limit_free_status == 0 && <div className='btns-box'>
<a className='consult consult-m' onClick={()=>this.qimoChatClick()}>
<i className='iconfont iconerji'></i>
<span>课程咨询</span>
</a>
<button className={'get-course btn'} onClick={() => {this.props.getCourse(info.course_id)}}>立即领取</button>
</div>
}
</div> </div>
) )
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
background-color: $bg_fff; background-color: $bg_fff;
display: flex; display: flex;
justify-content: space-between; 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; z-index: 3;
.consult { .consult {
...@@ -84,18 +84,26 @@ ...@@ -84,18 +84,26 @@
font-size: 12px; font-size: 12px;
} }
} }
.sign-up-now { .sign-up-now {
width: 50%; width: 50%;
color: #fff; color: #fff;
font-size: 16px; 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 { .learn-now {
background-color: #0099FF; background-color: #0099FF;
font-size: 16px; font-size: 16px;
color: #FFF; 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 { .btn-m {
width: 50%; width: 50%;
} }
......
...@@ -82,6 +82,22 @@ ...@@ -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 { .isbuy {
display: inline-block; display: inline-block;
width: 66px; width: 66px;
...@@ -254,3 +270,49 @@ ...@@ -254,3 +270,49 @@
left: 8px; 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;
}
& 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() {
const {user, history} = this.props
if (user.hasError) {
history.push('/passport')
}
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 = id => {
http.post(`${API.home}/sys/limitFree/receive`, {
course_id: id
})
.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.bind(this, id)}>立即学习</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 && !!Object.keys(tab).length && Object.keys(tab).map((item, index) => {
return (
<li key={index} className={index === tabActiveIndex ? 'active' : ''} style={navItemStyle}
onClick={e => this.changeTab(e, index)}>
<a href={`#category${item}`} target={'_self'}>{tab[item]}</a>
</li>
)
})
}
</ul>
<div className="next-cover"></div>
</nav>
<WhiteSpace/>
<div className="course-list">
<ul>
{
tab && !!Object.keys(tab).length && Object.keys(tab).map(key => {
return (
<li key={key} className={'category'}>
<h2 id={`category${key}`}>
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/category-icon.png"
alt=""/>
<span>{tab[key]}</span>
</h2>
<ul className={'courses'}>
{
courses && courses.length && courses.map((item, index) => {
if (item.category_id != key) {
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={this.getCourse.bind(this, item.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={'red'}>限时免费</span>
<span className={'origin-price'}>¥{item.price0}</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={'purchased'}>
<i className='iconfont iconRectangleCopy4'/>
<span>{item.play_times}人学习</span>
</div>
bottom = <div className="bottom">
<span>已购买</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 {
width: 20%;
text-align: center;
height: 100%;
line-height: 39px;
flex-shrink: 0;
&.active {
color: #09f;
border-bottom: 1px solid #09f;
}
}
}
.course-list {
border-top: 1px solid transparent;
}
.category {
padding: 12px 12px 0;
background: #fff;
margin-bottom: 8px;
}
h2 {
scroll-margin-top: 45px;
}
.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: 18px 0;
img {
width: 12px;
height: 12px;
margin-right: 6px;
}
span {
font-size: 16px;
color: #333;
}
}
.iconfont {
font-size: 12px;
margin-right: 4px;
}
.info {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
font-size: 12px;
color: #999;
.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;
}
}
}
.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;
}
& 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 './index.scss';
import {HeaderBar, VList} from '../../common' import { HeaderBar, VList } from '../../common'
import { http } from "@/utils"; import { http, dateCountDown } from "@/utils";
import {Link} from 'react-router-dom' import { Link } from 'react-router-dom'
import {Toast} from 'antd-mobile' import { Toast } from 'antd-mobile'
import {connect} from "react-redux" import { connect } from "react-redux"
import {getCourses} from './../detail/actions';
import Loading from '@/common/Loading' import Loading from '@/common/Loading'
class Purchased extends Component { class Purchased extends Component {
...@@ -19,6 +18,7 @@ class Purchased extends Component { ...@@ -19,6 +18,7 @@ class Purchased extends Component {
componentDidMount() { componentDidMount() {
this.getList() this.getList()
this.limitFreeCountDown()
} }
// 获取订单 // 获取订单
...@@ -36,19 +36,25 @@ class Purchased extends Component { ...@@ -36,19 +36,25 @@ class Purchased extends Component {
} }
toCourseDetail = (id) => { toCourseDetail = (id) => {
const { dispatch, history } = this.props; const {dispatch, history} = this.props;
// dispatch(getCourses(id, () => { // dispatch(getCourses(id, () => {
history.push(`/detail?id=${id}`) history.push(`/detail?id=${id}`)
// })); // }));
} }
limitFreeCountDown = timestamp => {
//todo 联调
const later = new Date(timestamp)
const earlier = Date.now()
return dateCountDown(later, earlier)
}
render() { render() {
const {user} = this.props const {user} = this.props
const uid = user && user.data && user.data.uid const uid = user && user.data && user.data.uid
return ( return (
<div className='purchased-box'> <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}> <Loading isLoading={this.state.isLoading}>
{ {
this.state.data && this.state.data.length > 0 ? this.state.data && this.state.data.length > 0 ?
...@@ -59,9 +65,7 @@ class Purchased extends Component { ...@@ -59,9 +65,7 @@ class Purchased extends Component {
const Info = ( const Info = (
<div className="info"> <div className="info">
<p className='title' onClick={() => this.toCourseDetail(item.course_id)}> <p className='title' onClick={() => this.toCourseDetail(item.course_id)}>
{/* <Link to={`/detail?id=${item.course_id}`}> */}
{item.course_title} {item.course_title}
{/* </Link> */}
</p> </p>
<p className='contact text-overflow-2'>{item.simpledescription}</p> <p className='contact text-overflow-2'>{item.simpledescription}</p>
...@@ -80,10 +84,11 @@ class Purchased extends Component { ...@@ -80,10 +84,11 @@ class Purchased extends Component {
</div> </div>
) )
const status = ( const status = (
item.is_aist && <span className='status'>返现</span> // item.is_aist && <span className='status'>返现</span>
<span className='limit-free-status'>{'*'}{'*'}{'*'}分后过期</span>
) )
const courseExpire = ( const courseExpire = (
item.course_expire && item.course_expire!='' && item.course_expire && item.course_expire != '' &&
<span className='course-expire'>{item.course_expire}</span> <span className='course-expire'>{item.course_expire}</span>
) )
return ( return (
......
html, body, #root { html, body, #root {
height: 100%!important; height: 100% !important;
} }
.purchased-box { .purchased-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -110,17 +111,32 @@ html, body, #root { ...@@ -110,17 +111,32 @@ html, body, #root {
color: #fff; color: #fff;
background: linear-gradient(to bottom, #FF4000, #FD7700); background: linear-gradient(to bottom, #FF4000, #FD7700);
} }
.course-expire{
.limit-free-status {
position: absolute;
right: 0;
top: 8px;
width: 112px;
height: 20px;
background: #FF0000;
border-radius: 10px 0 0 10px;
line-height: 20px;
color: #fff;
text-align: center;
font-size: 11px;
}
.course-expire {
display: inline-block; display: inline-block;
text-align: center; text-align: center;
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
left: 0; left: 0;
width:92px; width: 92px;
height:20px; height: 20px;
line-height: 21px; line-height: 21px;
background-color: #FF3A3A; background-color: #FF3A3A;
border-radius:0 10px 10px 0; border-radius: 0 10px 10px 0;
color: #fff; color: #fff;
font-size: 12px; font-size: 12px;
} }
......
import React, { Component } from 'react' import React, { Component } from 'react'
import HeaderBar from '@/common/HeaderBar' import HeaderBar from '@/common/HeaderBar'
import './video.scss' 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 { http, getParam, browser } from '@/utils'
import Recommendation from './recommendation' import Recommendation from './recommendation'
import VideoCatalog from './video-catalog' import VideoCatalog from './video-catalog'
...@@ -9,8 +9,8 @@ import DatumCatalog from './datum-catalog' ...@@ -9,8 +9,8 @@ import DatumCatalog from './datum-catalog'
import { Toast } from 'antd-mobile' import { Toast } from 'antd-mobile'
import videojs from 'video.js' import videojs from 'video.js'
import 'video.js/dist/video-js.min.css' import 'video.js/dist/video-js.min.css'
import { Modal } from "antd-mobile" import { Modal} from "antd-mobile"
import { Loading } from '@/common' import { Loading} from '@/common'
import { connect } from "react-redux" import { connect } from "react-redux"
import jsCookie from 'js-cookie' import jsCookie from 'js-cookie'
import Single from "@/components/detail/single"; import Single from "@/components/detail/single";
...@@ -103,10 +103,13 @@ class Video extends Component { ...@@ -103,10 +103,13 @@ class Video extends Component {
singleType: 1,// 单集购买需要 singleType: 1,// 单集购买需要
nowPrice: 0,// 单集购买需要 nowPrice: 0,// 单集购买需要
laterPrice: 0,// 单集购买需要 laterPrice: 0,// 单集购买需要
limitFreeNoPromptChecked: false,
showLimitFreePopup: true //todo 联调
} }
componentDidMount() { componentDidMount() {
console.log(this.props.location.hash)
if (window.location.protocol === 'https:') { if (window.location.protocol === 'https:') {
window.location.replace('http' + window.location.href.slice(5)) window.location.replace('http' + window.location.href.slice(5))
return return
...@@ -356,9 +359,9 @@ class Video extends Component { ...@@ -356,9 +359,9 @@ class Video extends Component {
this.ws.addEventListener('message', e => { this.ws.addEventListener('message', e => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
data.code == 4040 && (this.reconnect = false); data.code == 4040 && (this.reconnect = false);
if(data.code === 0) { if (data.code === 0) {
console.log("上次的学习记录" + JSON.stringify(data)); console.log("上次的学习记录" + JSON.stringify(data));
if(data.data && data.data.position) { if (data.data && data.data.position) {
this.player.currentTime(data.data.position); this.player.currentTime(data.data.position);
} }
} }
...@@ -367,12 +370,12 @@ class Video extends Component { ...@@ -367,12 +370,12 @@ class Video extends Component {
sendMessage = message => { sendMessage = message => {
let readyState = this.ws.readyState, _this = this; let readyState = this.ws.readyState, _this = this;
if(readyState === 1) { if (readyState === 1) {
this.ws && this.ws.send(JSON.stringify(message)) this.ws && this.ws.send(JSON.stringify(message))
}else if(readyState === 3) { } else if (readyState === 3) {
this.ws.close(); this.ws.close();
this.ws = null; this.ws = null;
let reconnect = setTimeout(function() { let reconnect = setTimeout(function () {
clearTimeout(reconnect); clearTimeout(reconnect);
reconnect = null; reconnect = null;
_this.ws = new WebSocket(PROCESS_URL); _this.ws = new WebSocket(PROCESS_URL);
...@@ -489,7 +492,7 @@ class Video extends Component { ...@@ -489,7 +492,7 @@ class Video extends Component {
this.player.on('play', () => { this.player.on('play', () => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state const {videoList, activeIndex, vCourseId, course = {}} = this.state
// 当视频播放时 看是否是第一次播放(初次进入页面 刷新页面 切换视频 都是第一次播放 需要获取上次的播放时间) // 当视频播放时 看是否是第一次播放(初次进入页面 刷新页面 切换视频 都是第一次播放 需要获取上次的播放时间)
if(this.isCurrentVideoFirstPlay) { if (this.isCurrentVideoFirstPlay) {
// 当某些原因导致视频暂停时(用户暂停 网络不好等) 再播放时不需要发送 // 当某些原因导致视频暂停时(用户暂停 网络不好等) 再播放时不需要发送
this.isCurrentVideoFirstPlay = false; this.isCurrentVideoFirstPlay = false;
// 发送消息 recent_learn // 发送消息 recent_learn
...@@ -504,7 +507,7 @@ class Video extends Component { ...@@ -504,7 +507,7 @@ class Video extends Component {
is_live: 0, is_live: 0,
})) }))
} }
if(!this.timer) { if (!this.timer) {
this.setupTimer(); this.setupTimer();
} }
}) })
...@@ -519,7 +522,7 @@ class Video extends Component { ...@@ -519,7 +522,7 @@ class Video extends Component {
this.count = this.watchSec = 0; this.count = this.watchSec = 0;
this.countSchedule(); // 计算进度 -- 播放完毕 this.countSchedule(); // 计算进度 -- 播放完毕
// 返现课程才出现打卡记录 // 返现课程才出现打卡记录
if(this.state.course.is_aist) { if (this.state.course.is_aist) {
this.getShareProgressInfo() this.getShareProgressInfo()
} }
clearInterval(this.timer); clearInterval(this.timer);
...@@ -733,10 +736,9 @@ class Video extends Component { ...@@ -733,10 +736,9 @@ class Video extends Component {
} }
} }
render() { render() {
let {match, location, history} = this.props 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} = this.state;
let toHref = ''; let toHref = '';
if (location.state && location.state.to && location.state.to === 'detail') { if (location.state && location.state.to && location.state.to === 'detail') {
toHref = `/detail?id=${course.course_id}` toHref = `/detail?id=${course.course_id}`
...@@ -851,7 +853,6 @@ class Video extends Component { ...@@ -851,7 +853,6 @@ class Video extends Component {
activeIndex={this.state.activeIndex} activeIndex={this.state.activeIndex}
selectVideo={this.selectVideo} selectVideo={this.selectVideo}
videoCatalog={videoList} videoCatalog={videoList}
isAist={course.is_aist}
{...props} {...props}
/> />
); );
...@@ -869,7 +870,41 @@ class Video extends Component { ...@@ -869,7 +870,41 @@ class Video extends Component {
closeShareModal={() => this.setState({isShowShareModal: false})} closeShareModal={() => this.setState({isShowShareModal: false})}
data={this.state.shareData} data={this.state.shareData}
/> />
{
showLimitFreePopup &&
<div className={'limit-free-cover'}>
<div className="free-popup">
<div className="title">
<span>想领取【AI工程师必备干货礼包】? 想深入了解进阶课程? 职业前景不明朗? 资深规划师免费为你服务!</span>
</div>
<div className={'des'}>
<img className="qrcode"
src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/tinypng-common/right_weixin.png" alt=''/>
<span>长按/扫码识别</span>
<span>添加时请备注<span>142</span>哦</span>
<div className="no-prompt">
<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
})
localStorage.setItem('neverShowLimitFreePopup', '1')
}}/>
</div>
</div>
}
</div> </div>
); );
} }
......
...@@ -20,7 +20,6 @@ $tabHeight: 44px; ...@@ -20,7 +20,6 @@ $tabHeight: 44px;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
.vjs-custom-play-button { .vjs-custom-play-button {
position: absolute; position: absolute;
top: 50%; top: 50%;
...@@ -33,8 +32,8 @@ $tabHeight: 44px; ...@@ -33,8 +32,8 @@ $tabHeight: 44px;
} }
} }
&.vjs-has-started{ &.vjs-has-started {
.vjs-custom-play-button-cover{ .vjs-custom-play-button-cover {
bottom: 2.9em; bottom: 2.9em;
} }
} }
...@@ -281,3 +280,123 @@ $tabHeight: 44px; ...@@ -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 [ ...@@ -299,4 +299,10 @@ export default [
exact: true, exact: true,
component: loadable(() => import('@/components/college/courseList')) component: loadable(() => import('@/components/college/courseList'))
}, },
//限时免费落地页
{
path:'/free',
exact: true,
component: loadable(() => import(/*limit-free*/'@/components/limit-free'))
}
] ]
import jsCookie from "js-cookie"; import jsCookie from "js-cookie";
import {
differenceInDays,
differenceInHours,
differenceInMinutes,
differenceInSeconds
} from 'date-fns'
export const getParam = (key, str) => { export const getParam = (key, str) => {
...@@ -8,7 +14,9 @@ export const getParam = (key, str) => { ...@@ -8,7 +14,9 @@ export const getParam = (key, str) => {
return (found = re.exec(_s)) ? found[2] : null; return (found = re.exec(_s)) ? found[2] : null;
} }
const html = content => ({__html: htmlDecode(content)}) const html = content => ({
__html: htmlDecode(content)
})
const htmlDecode = content => { const htmlDecode = content => {
let e = document.createElement('div'); let e = document.createElement('div');
...@@ -93,8 +101,38 @@ const isLogin = (function () { ...@@ -93,8 +101,38 @@ const isLogin = (function () {
return jsCookie.get('uid') && jsCookie.get('token') 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 {
export {default as wxShare} from './wechat/share' default as http
export {html, initCaptcha, validateTel, validateEmail, browser, isLogin} }
export {default as SendMessageToApp} from './app' 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