Commit 840fffc3 by zhanghaozhe

微信登录无痕验证

parent c6d13618
import React, { Component } from 'react'
import Routes from './router'
import cookie from 'js-cookie'
import { connect } from "react-redux"
import { setCurrentUser, startFetchUser } from "@/store/userAction"
import { initialState } from "@/store/userReducer"
import { withRouter, Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { setCurrentUser, startFetchUser } from '@/store/userAction'
import {
updateCaptchaState,
closeCaptchaModal,
showCaptchaModal,
validationPassed,
} from '@/store/no-trace-validation/reducer'
import { initialState } from '@/store/userReducer'
import { withRouter } from 'react-router-dom'
import { compose } from 'redux'
import { getParam, http, browser, loadScript, getTimestamp } from "@/utils"
import { Toast } from "antd-mobile"
import { getParam, http, browser, loadScript, getTimestamp } from '@/utils'
import { Toast } from 'antd-mobile'
import { addDays } from 'date-fns'
import stringify from 'json-stringify-safe'
//拦截ajax请求,返回mock数据
/*import mock from '@/utils/mock'
mock()*/
// 默认样式
import './assets/css/index.scss'
// iconfont
import './assets/font/iconfont.css'
class App extends Component {
constructor(props) {
super(props)
......@@ -30,7 +34,7 @@ class App extends Component {
isShowActivityEntry: 0,
jumpUrl: '',
entryImage: '',
isNeedCaptcha: true,
mkey: '',
}
}
......@@ -45,10 +49,14 @@ class App extends Component {
firstLoad = true
componentWillMount() {
let url = window.location.href;
let url = window.location.href
if (url.indexOf('ccode') > -1) {
if (!getParam('ccode').includes('%')) {
cookie.set('ccode', getParam('ccode'), {expires: 1, domain: '.julyedu.com', path: '/'})
cookie.set('ccode', getParam('ccode'), {
expires: 1,
domain: '.julyedu.com',
path: '/',
})
}
}
}
......@@ -61,23 +69,19 @@ class App extends Component {
//平台信息
!getParam('version') && cookie.set('plat', '5', {domain: '.julyedu.com'})
if (browser.isWeixin && browser.isIOS) {
sessionStorage.setItem('enter_url', window.location.href)
}
this.utm()
const {history} = this.props
this.getUser()
this.setNavigationRecord(this.props.location, this.props.history.action)
this.setPreviousLocation()
history.listen((location, action) => {
this.firstLoad = false
this.setNavigationRecord(location, action)
......@@ -87,7 +91,10 @@ class App extends Component {
this.getUser()
}
if (location.pathname.startsWith('/passport')) {
window.localStorage.setItem('binding_redirect', stringify(this.previousLocation))
window.localStorage.setItem(
'binding_redirect',
stringify(this.previousLocation),
)
}
const {pathname, state} = location
if (pathname.startsWith('/passport')) {
......@@ -107,27 +114,46 @@ class App extends Component {
})
}
componentDidUpdate(prevProps, prevState) {
this.setPreviousLocation()
if (!this.props.user.hasError && getParam('redirect')) {
window.location.href = getParam('redirect')
}
const {noTraceValidation: {isShowCaptcha}} = this.props
if (prevProps.noTraceValidation.isShowCaptcha !== isShowCaptcha && isShowCaptcha) {
typeof nvcReset === 'function' && nvcReset()
typeof getNC === 'function' && getNC().then(() => {
_nvc_nc.reset()
})
}
}
// 获取宝箱阶段
getStage = () => {
let ccode = cookie.get('ccode')
http.get(`${API.home}/sys/anniversary/floating?ccode=${ccode}`).then(res => {
const {code, data, msg} = res.data
if (code === 200) {
this.setState({
isShowActivityEntry: data.is_show,
jumpUrl: data.jump_url,
entryImage: data.image_path,
})
} else {
Toast.info(msg, 2)
}
})
http
.get(`${API.home}/sys/anniversary/floating?ccode=${ccode}`)
.then(res => {
const {code, data, msg} = res.data
if (code === 200) {
this.setState({
isShowActivityEntry: data.is_show,
jumpUrl: data.jump_url,
entryImage: data.image_path,
})
} else {
Toast.info(msg, 2)
}
})
}
umengStatistic = () => {
// 友盟统计
const script = document.createElement('script')
script.src = 'https://s22.cnzz.com/z_stat.php?id=1265696973&web_id=1265696973'
script.src =
'https://s22.cnzz.com/z_stat.php?id=1265696973&web_id=1265696973'
script.language = 'JavaScript'
document.body.appendChild(script)
}
......@@ -138,18 +164,26 @@ class App extends Component {
setNavigationRecord = (location, action) => {
const {pathname, search, hash} = location
let isLastRecord = location.pathname === (this.records.length && this.records[this.records.length - 1].pathname)
let needHistoryMutation = location.pathname !== this.previousLocation.pathname
let isLastRecord =
location.pathname ===
(this.records.length && this.records[this.records.length - 1].pathname)
let needHistoryMutation =
location.pathname !== this.previousLocation.pathname
switch (action) {
case 'POP':
if (needHistoryMutation) {
this.firstLoad ? this.records.push({pathname, search, hash}) : this.records.pop()
this.firstLoad
? this.records.push({pathname, search, hash})
: this.records.pop()
} else {
this.records.length ? (this.records[this.records.length - 1] = location) : this.records = [location]
this.records.length
? (this.records[this.records.length - 1] = location)
: (this.records = [location])
}
break
case 'REPLACE':
this.records.length > 1 && (this.records[this.records.length - 1] = {pathname, search, hash})
this.records.length > 1 &&
(this.records[this.records.length - 1] = {pathname, search, hash})
break
default:
!isLastRecord && this.records.push({pathname, search, hash})
......@@ -157,26 +191,25 @@ class App extends Component {
location.state && location.state.records
? (location.state.records = this.records)
: location.state ? location.state = {
: location.state
? (location.state = {
...location.state,
records: this.records,
} : (location.state = {records: this.records})
})
: (location.state = {records: this.records})
}
utm = () => {
// utm统计 m站全站统计广告投放、以及统计详情页浏览
let zhihu_cb = getParam('cb')
if (zhihu_cb) {
let data = {
'zhihu_cb': zhihu_cb,
zhihu_cb: zhihu_cb,
}
http.post(`${API['home']}/sys/zhihu/firstRecord`, data)
.then(res => {
})
http.post(`${API['home']}/sys/zhihu/firstRecord`, data).then(res => {
})
}
}
......@@ -188,13 +221,18 @@ class App extends Component {
let code = getParam('code')
let oid = getParam('oid')
if (code && !oid) {
http.get(`${API["passport-api"]}/m/wx_loginInfo/code/${code}?redirect=${encodeURIComponent(window.location.href)}`)
http.get(`${API['passport-api']}/m/wx_loginInfo/code/${code}?redirect=${encodeURIComponent(window.location.href)}`)
.then(res => {
let data = res.data
if (data.errno == 200) {
if (data.data.is_check) {
this.setupNoTraceValidate(data.data.mkey)
this.props.updateCaptchaState({
isNeedValidation: true
})
this.setState({
mkey: data.data.mkey,
});
this.setupNoTraceValidate()
} else {
if (data.data['is_bind_mobile']) {
window.location.assign(data.data.url)
......@@ -207,8 +245,6 @@ class App extends Component {
this.props.setCurrentUser(initialState)
}
})
} else {
if (this.props.location.pathname !== '/my') {
http.get(`${API.home}/m/user_info_sample/0`).then(res => {
......@@ -225,18 +261,17 @@ class App extends Component {
cookie.set('uid', uid, {expires, domain: '.julyedu.com', path: '/'})
cookie.set('token', token, {expires, domain: '.julyedu.com', path: '/'})
/*const search = new URLSearchParams(window.location.search)
const search = new URLSearchParams(window.location.search)
search.delete('code')
search.delete('aa')
if (search.has('state') && search.get('state') === 'STATE') {
search.delete('state')
}
const loc = window.location
loc.replace(loc.origin + loc.pathname + '?' + search.toString() + loc.hash)*/
loc.replace(loc.origin + loc.pathname + '?' + search.toString() + loc.hash)
}
setupNoTraceValidate = mkey => {
setupNoTraceValidate = () => {
let src = '//g.alicdn.com/sd/nvc/1.1.112/guide.js?t=' + getTimestamp(60 * 1000)
const appkey = 'FFFF0N000000000090FC'
const scene = 'nvc_register_h5'
......@@ -262,42 +297,44 @@ class App extends Component {
let timer = setInterval(() => {
if (window.getNVCVal) {
let NVC = window.getNVCVal()
this.requestRegister({mkey, NVC})
this.requestRegister(NVC)
clearInterval(timer)
}
}, 1000)
})
}
requestRegister = data => {
http.post(`${API["passport-api"]}/m/wxRegister`, data)
.then(res => {
const {errno, msg, data} = res.data
if (errno === 200) {
this.handleLoginResponse(data)
} else if (errno === 5002) {
nvcReset && nvcReset()
getNC().then(() => {
})
} else {
Toast.info(msg)
}
})
}
componentDidUpdate() {
requestRegister = (NVC) => {
http.post(`${API['passport-api']}/m/wxRegister`, {
mkey: this.state.mkey,
NVC,
}).then(res => {
const {errno, msg, data} = res.data
if (errno === 200) {
this.setPreviousLocation()
this.props.validationPassed()
this.handleLoginResponse(data)
if (!this.props.user.hasError && getParam('redirect')) {
window.location.href = getParam('redirect')
}
} else if (errno === 5002) {
this.props.updateCaptchaState({
isShowCaptcha: true
})
typeof nvcReset === 'function' && nvcReset()
typeof getNC === 'function' && getNC().then(() => {
_nvc_nc.reset()
})
} else {
Toast.info(msg)
}
})
}
setPreviousLocation = () => {
const {location} = this.props
let isInBlacklist = this.pathnameBlacklist.some(item => location.pathname.startsWith(item))
let isInBlacklist = this.pathnameBlacklist.some(item =>
location.pathname.startsWith(item),
)
!isInBlacklist && (this.previousLocation = location)
}
......@@ -307,7 +344,8 @@ class App extends Component {
//移除红包统计cookie
this.removeShareCodeCookie()
const {
msg, data: {
msg,
data: {
avatar_file: avatar,
user_name: username,
is_vip: isVIP,
......@@ -378,7 +416,11 @@ class App extends Component {
if (lastCloseTime) {
const pastDate = new Date(parseInt(lastCloseTime))
const now = new Date()
if (now.getFullYear() > pastDate.getFullYear() || now.getMonth() > pastDate.getMonth() || now.getDate() > pastDate.getDate()) {
if (
now.getFullYear() > pastDate.getFullYear() ||
now.getMonth() > pastDate.getMonth() ||
now.getDate() > pastDate.getDate()
) {
this.getStage()
}
} else {
......@@ -387,29 +429,50 @@ class App extends Component {
}
render() {
const {isShowActivityEntry, entryImage, jumpUrl, isNeedCaptcha} = this.state
return <>
<Routes/>
{
!!isShowActivityEntry &&
<div className="year19-index">
<i className={'iconfont iconiconfront-2'} onClick={this.closeGlobalEntry}></i>
<a href={jumpUrl}>
<img src={entryImage} alt=""/>
</a>
</div>
}
{
isNeedCaptcha && <div id={'global-captcha'}></div>
}
</>
const {
isShowActivityEntry,
entryImage,
jumpUrl,
} = this.state
const {
noTraceValidation: {isShowCaptcha, isNeedValidation},
} = this.props
return (
<>
<Routes/>
{!!isShowActivityEntry && (
<div className="year19-index">
<i
className={'iconfont iconiconfront-2'}
onClick={this.closeGlobalEntry}
></i>
<a href={jumpUrl}>
<img src={entryImage} alt=""/>
</a>
</div>
)}
{isNeedValidation && isShowCaptcha && (
<div className="modal-cover">
<div className="modal">
<div>亲,系统正忙,滑动一下马上回来</div>
<div id={'global-captcha'}></div>
<i className="iconfont iconiconfront-2 close" onClick={this.props.closeCaptchaModal}></i>
</div>
</div>
)}
</>
)
}
}
export default compose(
connect(
state => ({user: state.user}),
{setCurrentUser, startFetchUser},
),
connect(({user, noTraceValidation}) => ({user, noTraceValidation}), {
setCurrentUser,
startFetchUser,
updateCaptchaState,
closeCaptchaModal,
showCaptchaModal,
validationPassed,
}),
withRouter,
)(App)
......@@ -10,25 +10,70 @@ $link-visited: #333; // 设置链接访问后的颜色
$main-color: #09f; // 主体颜色
// 字体
$font-family-zh: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
$font-family-zh: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
$font-family-en: Arial, sans-serif !default;
// 盒子模型
$box-model: border-box !default;
// z-index
$z-50: 50;
$z-100: 100;
$z-150: 150;
$z-200: 200;
$z-250: 250;
$z-max: 999999; //为了应付某些插件z-index 值过高的问题
// 全局设置
// --------------------------------------------------
//
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
html,
body,
div,
span,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
address,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
samp,
small,
strong,
sub,
sup,
var,
b,
i,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td {
border: 0 none;
font-size: inherit;
color: inherit;
......@@ -40,15 +85,23 @@ html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pr
max-height: 100000px;
}
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: normal;
}
em, strong {
em,
strong {
font-style: normal;
}
ul, ol, li {
ul,
ol,
li {
list-style: none;
}
......@@ -78,252 +131,12 @@ body {
padding-bottom: constant(safe-area-inset-bottom);
}
// Links
a {
text-decoration: none;
outline: none;
&:hover,
&:link,
&:focus {
text-decoration: none;
}
&:visited {
}
}
// 暂时放置样式,后期需处理
.homeImg {
display: block;
width: 100%;
}
// 字体颜色
.main-color {
color: $main-color;
}
.color333 {
color: #333
}
.color666 {
color: #666
}
.color999 {
color: #999
}
// 背景颜色
.bg-white {
background-color: #fff
}
// 间隔
.pt20 {
padding-top: 20px;
}
.pt30 {
padding-top: 30px;
}
.pt40 {
padding-top: 40px;
}
.pt50 {
padding-top: 50px;
}
.pt60 {
padding-top: 60px;
}
.plr20 {
padding-left: 0.2rem;
padding-right: 0.2rem;
}
// 请保证你的设计稿为750px宽,如果有其余字体大小,请在私有样式中设置
.font-20 {
font-size: 0.2rem;
}
.font-24 {
font-size: 0.24rem;
}
.font-26 {
font-size: 0.26rem;
}
.font-28 {
font-size: 0.28rem;
}
.font-30 {
font-size: 0.3rem;
}
.font-32 {
font-size: 0.32rem;
}
.font-34 {
font-size: 0.34rem;
}
.font-36 {
font-size: 0.36rem;
}
.font-38 {
font-size: 0.38rem;
}
.font-40 {
font-size: 0.4rem;
}
// 设置block
.block {
display: block;
}
.show {
display: inherit;
}
.hide {
display: none;
}
// 最外层页面设置
.box {
max-width: 10rem;
margin-left: auto;
margin-right: auto;
}
// 半透明弹层
.alert-bg {
position: fixed;
z-index: $z-50;
width: 100%;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, .6);
display: none; //注意:默认隐藏!!
}
.alpha-bg {
position: fixed;
z-index: 100;
background: rgba(0, 0, 0, .7);
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.fixed-bottom {
position: fixed;
z-index: 99;
bottom: 0;
width: 100%;
}
// 布局相关
// 水平
.hor {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
// 水平居中
.hor-center {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
}
// 垂直居中
.ver-center {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
}
// 子元素内联垂直居中
.center-center {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
// 子元素块联水平垂直居中
.center-center-column {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
// 两边对齐
.space-between {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
// last-no-border
.last-no-border:last-child {
border: none;
background: none;
}
// 图片设置
img {
max-width: 100%;
}
.img-responsive {
display: block;
width: 100%;
}
// 这里主要应付 antd-mobile 的组件carousel 不能等比缩放的蛋疼问题
.home-swipe {
height: 40.625vw;
max-height: 406.25px;
}
// 文本控制类
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
// 字符溢出隐藏
.text-overflow-1 {
overflow: hidden;
......@@ -361,75 +174,6 @@ img {
-webkit-box-orient: vertical;
}
// 浮动控制
.cf {
&:before,
&:after {
content: '';
display: table;
}
&:after {
clear: both;
}
}
.fl {
float: left;
}
.fr {
float: right;
}
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.fixed {
position: fixed;
}
.z-50 {
z-index: 50;
}
.z-100 {
z-index: 100;
}
.z-150 {
z-index: 150;
}
.z-200 {
z-index: 200;
}
.z-250 {
z-index: 250;
}
.z-max {
z-index: 999999;
}
.overflow-h {
overflow: hidden;
}
// 元素绝对定位的垂直水平居中
.absolute-center {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
// radio 样式重写
input[type="radio"] {
position: relative;
......@@ -445,7 +189,7 @@ input[type="radio"] {
input[type="radio"]:before {
position: absolute;
content: '';
content: "";
display: block;
width: 0.36rem;
height: 0.36rem;
......@@ -461,7 +205,7 @@ input[type="radio"]:before {
input[type="radio"]:checked:after {
position: absolute;
z-index: 50;
content: '';
content: "";
display: block;
width: 0.36rem;
height: 0.36rem;
......@@ -476,7 +220,7 @@ input[type="radio"]:checked:after {
input[type="radio"]:checked:before {
position: absolute;
z-index: 100;
content: '';
content: "";
display: block;
width: 0.18rem;
height: 0.18rem;
......@@ -501,14 +245,14 @@ input[type="radio"]:checked:before {
font-weight: 600;
}
.am-modal-button-group-h {
.am-modal-button {
font-size: 15px;
}
}
.am-modal-alert-content, .am-modal-propmt-content {
.am-modal-alert-content,
.am-modal-propmt-content {
color: #333;
font-size: 15px;
}
......@@ -538,7 +282,69 @@ input[type="radio"]:checked:before {
}
}
#global-captcha{
height: 44px;
margin-bottom: 50px;
}
\ No newline at end of file
.modal-cover {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
.modal {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -65%);
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
width: 300px;
height: 130px;
text-align: center;
padding: 20px 20px;
background: #fff;
border-radius: 5px;
font-size: 16px;
color: #525c65;
& > div {
margin-bottom: 10px;
}
._nc {
height: 48px;
}
.stage {
height: 48px;
.slider {
height: 48px;
box-shadow: 0 0 3px #9a9a9a;
.label, .track, .button {
height: 48px;
}
}
}
}
.close {
position: absolute;
bottom: -80px;
left: 50%;
margin-left: -18px;
font-size: 36px;
color: #fff;
}
#global-captcha {
width: 100%;
height: 48px;
margin-bottom: 50px;
}
}
......@@ -9,6 +9,7 @@ import { CopyToClipboard } from "react-copy-to-clipboard";
import { Link } from 'react-router-dom'
import storage from 'store2'
import { CaptchaAli } from "@common/index"
import { showCaptchaModal } from "@/store/no-trace-validation/reducer";
class Invitation extends Component {
......@@ -94,7 +95,12 @@ class Invitation extends Component {
}
joinTeam = data => {
const {user, history} = this.props
const {user, history, noTraceValidation, showCaptchaModal} = this.props
if (noTraceValidation.isNeedValidation && !noTraceValidation.isShowCaptcha) {
showCaptchaModal()
return
}
if (user.hasError) {
if (browser.isWeixin) {
window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx23dac6775ac82877&redirect_uri=" + encodeURIComponent(window.location.href + "&aa=bb").toLowerCase() + "&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"
......@@ -103,33 +109,42 @@ class Invitation extends Component {
}
return
}
if (this.state.teamInfo.is_check && !this.state.isShowCaptcha) {
this.setState({
isShowCaptcha: true,
});
return
}
let _data = {team_code: getParam('team_code')}
if (data.token && data.sig) {
_data = {..._data, ...data}
}
http.post(`${API.home}/activity/anniversary/joinTeam`, _data)
.then(res => {
const {code, msg} = res.data
if (code === 200) {
Toast.info('加入成功', 2, null, false)
this.setState((state, props) => {
return {
teamInfo: {...state.teamInfo, ...{status: 2, is_check: false}},
isShowCaptcha: false,
}
});
})
} else if (code === 5002) {
this.state.instance && this.state.instance.reset()
} else if (code === 25015) {
this.setState({
isActivityEnd: true,
});
})
} else {
Toast.info(msg)
}
......@@ -265,7 +280,9 @@ function BottomButton({status, isActivityEnd, isAnswered, joinTeam, drawQuestion
export default compose(
connect(
({user}) => ({user}),
null,
({user, noTraceValidation}) => ({user, noTraceValidation}),
{
showCaptchaModal,
},
),
)(Invitation);
\ No newline at end of file
import { combineReducers } from 'redux';
import myCourses from '@/components/study/myCourses/reducers'
import courseInfo from '@/components/detail/reducers'
import user from './userReducer'
import country from '@/components/country/countryRedux'
import { combineReducers } from "redux"
import myCourses from "@/components/study/myCourses/reducers"
import courseInfo from "@/components/detail/reducers"
import user from "./userReducer"
import country from "@/components/country/countryRedux"
import intelligentRecommend from "@components/intelligent-recommend/store"
import anniversary2020Question from '@components/activity/2020-717/question/store/reducer' //2020周年庆活动答题页
import anniversary2020Question from "@components/activity/2020-717/question/store/reducer" //2020周年庆活动答题页
import noTraceValidation from "./no-trace-validation/reducer"
const reducer = combineReducers({
myCourses,
......@@ -14,6 +14,7 @@ const reducer = combineReducers({
country,
intelligentRecommend,
anniversary2020Question,
});
noTraceValidation,
})
export default reducer;
\ No newline at end of file
export default reducer
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