Commit 0f7d4c66 by xuzhenghua

detail

parents 3a1d0878 f8861e99
......@@ -17,6 +17,7 @@
"babel-preset-react-app": "^7.0.2",
"bfj": "6.1.1",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"crypto-js": "^3.1.9-1",
"css-loader": "1.0.0",
"date-fns": "^1.30.1",
"dotenv": "6.0.0",
......@@ -70,6 +71,7 @@
"style-loader": "0.23.1",
"terser-webpack-plugin": "1.2.2",
"url-loader": "1.1.2",
"video.js": "^7.5.4",
"webpack": "4.28.3",
"webpack-dev-server": "3.1.14",
"webpack-manifest-plugin": "2.0.4",
......
import React, {Component} from 'react'
import React, { Component } from 'react'
import Routes from './router'
import cookie from 'js-cookie'
import {api} from '@/utils'
import { http, api } from '@/utils';
import { connect } from 'react-redux';
import { setCurrentUser } from '@/store/userAction';
//拦截ajax请求,返回mock数据
/*import mock from '@/utils/mock'
......@@ -14,15 +17,57 @@ import './assets/css/index.scss';
// iconfont
import './assets/font/iconfont.css';
class App extends Component {
componentDidMount() {
export default class App extends Component{
componentDidMount(){
//平台信息
cookie.set('plat', '5')
http.get(`${api.home}/m/user_info`).then(res => {
this.props.setCurrentUser(this.storeUser(res))
})
}
storeUser = res => {
let payload
if (res.data.code === 200) {
const {
msg, data: {
avatar_file: avatar,
user_name: username,
is_vip: isVIP,
uid
}
} = res.data
payload = {
hasError: false,
msg,
data: {
username,
isVIP,
avatar,
uid
}
}
} else {
payload = {
hasError: true,
msg: res.data.msg
}
}
return payload
}
render(){
render() {
return <Routes/>
}
}
\ No newline at end of file
}
export default connect(
null,
{setCurrentUser}
)(App)
\ No newline at end of file
import React, { Component } from "react"
import React, { PureComponent } from "react"
import './accountLogin.scss'
import { Link } from "react-router-dom";
import { withFormik, FastField, Form } from "formik";
import { compose } from 'redux';
import { accountLogin } from '@/store/userAction';
import { connect } from "react-redux";
import Header from "../common/Header";
......@@ -11,8 +14,7 @@ import PasswordInput from '../common/passwordInput'
// import VeriCodeButton from '../common/veriCodeInput'
// import LoginWays from '../common/LoginWays'
class AccountLogin extends Component {
class AccountLogin extends PureComponent {
render() {
return (
......@@ -40,6 +42,7 @@ class AccountLogin extends Component {
render={({field}) => (
<PasswordInput
{...field}
autoComplete={'on'}
placeholder={'密码'}
/>
)}
......@@ -58,8 +61,24 @@ const formikConfig = {
account: '',
password: ''
}),
handleSubmit(values) {
console.log(values)
handleSubmit(values, formikBag) {
const {account: username, password} = values
const {props, props: {history}} = formikBag
props.accountLogin({
username, password
}).then(res => {
if (!res.hasError) {
let {from} = props.location.state || {from: {pathname: '/'}}
history.push(from.pathname)
}
})
}
}
export default withFormik(formikConfig)(AccountLogin)
export default compose(
connect(
state => ({user: state.user}),
{accountLogin}
),
withFormik(formikConfig)
)(AccountLogin)
import aes from 'crypto-js/aes'
import Crypto from 'crypto-js'
const key = Crypto.enc.Hex.parse('C7D590D00FA968A261BDD5B6CD40DDC2C0561338BF8B9197')
const iv = Crypto.enc.Hex.parse('19513F90B7A8875E469E82195F90EE99')
function encrypt(message) {
return aes.encrypt(message, key, {
iv,
mode: Crypto.mode.CBC,
}).toString()
}
function decrypt(encrypted) {
return Crypto.enc.Utf8.stringify(aes.decrypt(encrypted, key, {iv}))
}
export { encrypt, decrypt }
\ No newline at end of file
......@@ -17,12 +17,10 @@ class ForgotPassword extends Component {
}
setTel = (val) => {
console.log(val)
this.setState({tel: val})
}
setVerificationCode = (val) => {
console.log(val)
this.setState({verificationCode: val});
}
......
......@@ -18,7 +18,6 @@ class SetPassword extends Component {
handleChange = (val) => {
console.log(val)
this.setState({password: val});
}
......
export const login = (userInfo) => ({
type: 'LOGIN',
userInfo
})
export const logout = () => ({
type: 'LOGOUT'
})
export const requestLogin = payload => dispatch => {
}
\ No newline at end of file
const initialState = {
userName: '',
avatar: '',
uid: '',
isVIP: false
}
const userInfo = (state = initialState, action) => {
switch (action.type) {
case 'LOGIN':
return {
...state,
...action.userInfo
}
default:
return state
}
}
export default userInfo
\ No newline at end of file
.datum-catalog {
.prompt {
height: 30px;
line-height: 30px;
font-size: $font_12;
color: $color_333;
background-color: #FFF4CE;
text-align: center;
}
.file-name {
padding: 15px;
padding-bottom: 0;
}
& .file-name:last-of-type {
padding-bottom: 15px;
}
}
\ No newline at end of file
import React, { Component } from 'react';
import './datum-catalog.scss'
import { Accordion } from "antd-mobile";
class DatumCatalog extends Component {
static defaultProps = {
datum: [{
dir_name: '',
files: [{
file_name: '',
file_id: 0
}]
}]
}
render() {
return (
<div className='datum-catalog'>
<p className='prompt'>课程资料请到PC端播放页下载</p>
<Accordion>
{
this.props.datum.map((item, index) => {
return (
<Accordion.Panel header={item.dir_name} key={index}>
{
item.files.map(item => {
return (
<div key={item.file_id} className='file-name'>
{item.file_name}
</div>
)
})
}
</Accordion.Panel>
)
})
}
</Accordion>
</div>
);
}
}
export default DatumCatalog;
\ No newline at end of file
import React, { Component } from 'react';
import HeaderBar from '@/common/HeaderBar'
import './video.scss'
import { NavLink, Route } from 'react-router-dom';
import { http, api } from '@/utils'
import Recommendation from './recommendation'
import VideoCatalog from './video-catalog'
import DatumCatalog from './datum-catalog'
import { Toast } from 'antd-mobile';
import videojs from 'video.js'
import 'video.js/dist/video-js.min.css'
// import 'video.scss'
class Video extends Component {
video;
state = {
title: '视频',
courseId: 140,
video_catalog: [],
datum: []
}
componentDidMount() {
window.HELP_IMPROVE_VIDEOJS = false;
videojs(this.video, {
controls: true,
autoplay: true,
preload: 'auto',
bigPlayButton: true,
textTrackDisplay: false,
posterImage: false,
errorDisplay: false,
}, function () {
this.log.debug()
})
this.getVideoCatalog()
this.getDatumCatalog()
}
getVideoCatalog = () => {
http.get(`${api.home}/m/course/play/40`)
.then(res => {
const data = res.data
if (data.code === 200) {
this.setState({
video_catalog: data.data.lessons
})
} else {
Toast.info(data.msg)
}
})
}
getDatumCatalog() {
http.get(`${api.home}/m/course/data/40`)
.then(res => {
const data = res.data
if (data.code === 200) {
this.setState({
datum: data.data
})
} else {
Toast.info(data.msg)
}
})
}
render() {
let {match} = this.props
return (
<div className='play'>
<HeaderBar title={this.state.title}/>
<div className="video">
<video className={'video-js'} ref={el => this.video = el}>
<source src='/v2/ts/40/191/175d6e5a.m3u8' type='application/x-mpegURL'/>
</video>
</div>
<div className='tab'>
<div>
<NavLink to={`${match.url}/video`}
replace
activeClassName='active'
>视频</NavLink>
</div>
<div>
<NavLink to={`${match.url}/datum`}
replace
activeClassName='active'
>资料</NavLink>
</div>
</div>
{/*<Route path={`${match.path}/video`} render={props => {
return <VideoCatalog videoCatalog={this.state.video_catalog} {...props}/>
}}/>
<Route path={`${match.path}/datum`} render={props => {
return <DatumCatalog {...props} datum={this.state.datum}/>
}}/>
<Route render={props => {
return <Recommendation {...props} courseId={this.state.courseId}/>
}}/>*/}
</div>
);
}
}
export default Video;
\ No newline at end of file
import React, { Component } from 'react';
import './recommendation.scss'
import { http, api } from '@/utils'
import { Toast } from "antd-mobile";
import {VList} from '@/common';
const Bottom = ({item}) => {
return (
<div className='bottom'>
<span className='price'>¥{item.price1}</span>
<span className='stale-price'>¥{item.price0}</span>
</div>
)
}
class Recommendation extends Component {
state = {
num: 10,
list: []
}
componentDidMount() {
http.get(`${api.home}/m/play/recommend_course/${this.props.courseId}?num=${this.state.num}`)
.then(res => {
const data = res.data
if(data.code === 200){
this.setState({
list: data.data
})
}else {
Toast.info(data.msg)
}
})
}
handleClick = id => {
console.log(id)
}
render() {
return (
<div className='recommendation'>
<div className={'title'}>相关推荐</div>
<ul>
{
this.state.list.map(item => {
const Info = (
<div className="info">
<p className='title'>{item.course_title}</p>
<p className='des'>{item.simpledescription}</p>
<Bottom
item={item}
/>
</div>
)
return (
<VList
key={item.course_id}
img={item.image_name}
handleClick={this.handleClick}
info={Info}
id={item.course_id}
/>
)
})
}
</ul>
</div>
);
}
}
export default Recommendation;
\ No newline at end of file
.recommendation {
padding-top: 20px;
& > .title {
font-size: $font_16;
padding-left: 15px;
}
.info {
display: flex;
flex-wrap: wrap;
.des {
font-size: $font_14;
line-height: 16px;
align-self: flex-start;
}
.price {
color: $red;
font-size: $font_16;
margin-right: 14px;
}
.stale-price {
text-decoration: line-through;
color: $color_999;
font-size: $font_12;
}
.bottom {
align-self: flex-end;
}
}
}
\ No newline at end of file
import React, { Component } from 'react';
import './video-catalog.scss'
import classnames from 'classnames'
class VideoCatalog extends Component {
render() {
return (
<div className='video-catalog'>
<ul>
{
this.props.videoCatalog.map(item => {
return (
<li key={item.id}>
<span className="title">{item.name}</span>
<span className='duration'>{item.duration}</span>
<i className={classnames(`iconfont`,
[item.video_auth === 0
? 'iconiconfront-74'
: 'iconiconfront-35'],
)}/>
</li>
)
})
}
</ul>
</div>
);
}
}
export default VideoCatalog;
\ No newline at end of file
.video-catalog {
li {
height: 44px;
padding: 0 15px;
line-height: 44px;
border-top: 1px solid #E7EAF1;
border-bottom: 1px solid #E7EAF1;
&.active {
background-color: #F5FBFF;
.title, .duration{
color: $active;
}
}
}
.title {
display: inline-block;
width: 50%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: $font_14;
margin-right: 26px;
}
.duration {
display: inline-block;
font-size: 11px;
overflow: hidden;
}
.iconfont {
float: right;
}
}
\ No newline at end of file
$tabHeight: 44px;
.play {
.video {
width: 100%;
height: 215px;
background-color: $black;
video {
width: 100%;
height: 100%;
}
}
.tab {
height: $tabHeight;
max-height: $tabHeight;
line-height: $tabHeight;
text-align: center;
background: #F7F9FC;
flex: 1 0 auto;
display: flex;
justify-content: center;
& > div {
flex: 1 0 auto;
}
a {
display: inline-block;
height: $tabHeight;
font-size: $font_16;
}
}
.active {
color: $active;
border-bottom: 1px solid $active;
}
}
\ No newline at end of file
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux'
import { createStore, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import rootReducers from './store'
import App from './App'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
rootReducers,
applyMiddleware(thunk, logger)
composeEnhancers(
applyMiddleware(thunk, logger)
)
)
......
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import RouterConfig from './router-config'
import PrivateRoute from './privateRoute'
export default function () {
......@@ -8,9 +9,9 @@ export default function () {
<Router>
<Switch>
{RouterConfig.map((item, index) => {
let {CustomRoute, ...rest} = item
if (CustomRoute) {
return <CustomRoute {...rest}/>
let {isPrivate, ...rest} = item
if (isPrivate) {
return <PrivateRoute {...rest} key={index}/>
} else {
return (
<Route {...rest} key={index}/>
......
import React from 'react';
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import jsCookie from 'js-cookie'
const PrivateRoute = ({component: Component, path, user, ...rest}) => {
let authenticated = jsCookie.get('token') && jsCookie.get('uid')
return (
<Route {...rest} render={props => {
return authenticated
? <Component {...props}/>
: <Redirect to={{
pathname: '/passport/account-login',
state: {from: props.location}
}}/>
}}/>
);
};
export default connect(
state => ({user: state.user}),
null
)(PrivateRoute);
\ No newline at end of file
......@@ -17,6 +17,7 @@ import Passport from '@/components/passport'
const Coupons = loadable(() => import(/* webpackChunkName: 'coupons'*/ '@/components/coupons'))
const Study = loadable(() => import(/* webpackChunkName: 'study'*/'@/components/study'))
const Video = loadable(() => import(/* webpackChunkName: 'video'*/'@/components/video'))
export default [
{
......@@ -67,7 +68,8 @@ export default [
},
{
path: '/coupons',
component: Coupons
component: Coupons,
isPrivate: true
},
{
path: '/shopcart',
......@@ -81,4 +83,8 @@ export default [
path: '/passport',
component: Passport
},
{
path: '/play',
component: Video
},
]
\ No newline at end of file
......@@ -8,10 +8,12 @@ module.exports = function (app) {
config[item]['development'], {
target: config[item]['test'],
changeOrigin: true,
// secure: false,
pathRewrite: {
[`^${config[item]['development']}`]: ''
},
...item['proxy']
cookieDomainRewrite: 'localhost',
...config[item]['proxy']
}
))
})
......
import { combineReducers } from 'redux';
import myCourses from '@/components/study/myCourses/reducers'
import courseInfo from '@/components/detail/reducers'
import user from './userReducer'
const reducer = combineReducers({
myCourses,
courseInfo
courseInfo,
user
});
export default reducer;
\ No newline at end of file
import { http, api } from '@/utils';
import { encrypt } from '@/components/passport/encryption';
import jsCookie from 'js-cookie'
const accountLogin = user => dispatch => {
return http.post(`${api['passport-api']}/user_login`, {
user_name: user.username,
password: encrypt(user.password),
is_encrypt: 1
}).then(res => {
const data = res.data
let payload
if (data.errno === 0) {
const {user_name: username, avatar_file: avatar, ...rest} = data.data.user_info
payload = {
hasError: false,
msg: data.msg,
data: {username, avatar, ...rest}
}
} else {
payload = {
hasError: true,
msg: data.msg
}
}
dispatch(setCurrentUser(payload))
return payload
})
}
/*
const CAPTCHA_LOGIN = 'CAPTCHA_LOGIN'
const captchaLogin = payload => dispatch => {
return http.post(`${api['passport-api']}/`)
}
*/
const SET_CURRENT_USER = 'SET_CURRENT_USER'
const setCurrentUser = payload => ({
type: SET_CURRENT_USER,
payload
})
const LOGOUT = 'LOGOUT'
const logout = () => dispatch => {
jsCookie.remove('token')
jsCookie.remove('uid')
dispatch(setCurrentUser({}))
}
export {
accountLogin,
SET_CURRENT_USER,
setCurrentUser
}
\ No newline at end of file
import { SET_CURRENT_USER } from '@/store/userAction';
const initialState = {
hasError: false,
msg: '',
data: {
username: '',
avatar: '',
isVip: false,
token: '',
email: '',
uid: ''
}
}
export default function (state = initialState, action) {
switch (action.type) {
case SET_CURRENT_USER:
return action.payload
default:
return state
}
}
\ No newline at end of file
export { default as http } from './http'
export { default as api } from './api'
// 计算时间相差fn(过去距离当前时间)
export const computingTime = (pastTime) => {
var currentTime = (new Date()).getTime(),
distanceTime = currentTime - pastTime,
// 计算相差天数
days = Math.floor(distanceTime / (24 * 3600 * 1000)),
// 计算相差小时数
leave1 = distanceTime % (24 * 3600 * 1000),
hours = Math.floor(leave1 / (3600 * 1000)),
// 计算相差分钟数
leave2 = leave1 % (3600 * 1000),
minutes = Math.floor(leave2 / (60 * 1000)),
// 计算相差毫秒数
leave3 = leave2 % (60 * 1000),
seconds = Math.round(leave3 / 1000),
// 处理返回格式
dayStr = days <= 0 ? "" : days + "天",
hourStr = hours <= 0 ? "" : hours + "小时",
minuteStr = minutes <= 0 ? "" : minutes + "分钟",
secondStr = (days <= 0 && hours <= 0 && minutes <= 0) ? "刚刚" : "前";
// secondStr=seconds==0?"":seconds+"秒";
if (days >= 1) {
return dayStr + '前';
} else {
return dayStr + hourStr + minuteStr + secondStr;
}
}
// 时间倒计时 (未来距离现在)
export const timeDown = (endDate) => {
var now = new Date();
var leftTime = endDate - now.getTime();
var leftsecond = parseInt(leftTime / 1000);
var day1 = Math.floor(leftsecond / (60 * 60 * 24));
var hour = Math.floor((leftsecond - day1 * 24 * 60 * 60) / 3600);
var minute = Math.floor((leftsecond - day1 * 24 * 60 * 60 - hour * 3600) / 60);
var second = Math.floor(leftsecond - day1 * 24 * 60 * 60 - hour * 3600 - minute * 60);
hour = hour >= 10 ? hour : '0' + hour;
minute = minute >= 10 ? minute : '0' + minute;
second = second >= 10 ? second : '0' + second;
return day1 + '天' + hour + ':' + minute + ':' + second;
return leftTime;
}
export { html }
export const isPhone = ($poneInput) => {
var myreg = /^[1][3,4,5,7,8][0-9]{9}$/;
if (!myreg.test($poneInput)) {
return true;
} else {
return false;
}
}
export const getParam = (key, str) => {
const _s = str ? str : location.href;
const re = new RegExp(`(?:\\?|#|&)(${key})=([^=&#\\?]+)`, 'ig');
let found;
return (found = re.exec(_s)) ? found[2] : null;
}
\ No newline at end of file
}
const html = content => ({__html: htmlDecode(content)})
const htmlDecode = content => {
let e = document.createElement('div');
e.innerHTML = content;
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
......@@ -11,6 +11,20 @@ const config = {
production: 'https://search.julyedu.com',
proxy: {}
},
v2: {
development: '/v2',
test: 'https://v2.julyedu.com',
production: 'https://search.julyedu.com',
proxy: {
secure: false,
}
},
'passport-api': {
development: '/passport-api',
test: 'http://passport-test.julyedu.com',
production: 'http://passport.julyedu.com',
proxy: {}
},
}
module.exports = config
\ 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