diff --git a/src/application/Singer/index.js b/src/application/Singer/index.js index f07c0cc..584152c 100644 --- a/src/application/Singer/index.js +++ b/src/application/Singer/index.js @@ -1,17 +1,17 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { Container } from './style'; -import Header from '../../baseUI/header/index' +import React, { useState, useEffect, useRef } from "react"; +import { Container } from "./style"; +import Header from "../../baseUI/header/index"; import { ImgWrapper, CollectButton, SongListWrapper, BgLayer } from "./style"; -import Scroll from '../../baseUI/scroll/index'; -import { HEADER_HEIGHT } from './../../api/config'; -import { getSingerInfo } from './store/actionCreators'; -import { connect } from 'react-redux'; -import Loading from './../../baseUI/loading/index'; -import { EnterLoading } from '../Singers/style'; -import {changeEnterLoading} from './store/actionCreators'; -import { CSSTransition } from 'react-transition-group'; -import SongsList from '../SongList/'; -import MusicNote from '../../baseUI/music-note/index'; +import Scroll from "../../baseUI/scroll/index"; +import { HEADER_HEIGHT } from "./../../api/config"; +import { getSingerInfo } from "./store/actionCreators"; +import { connect } from "react-redux"; +import Loading from "./../../baseUI/loading/index"; +import { EnterLoading } from "../Singers/style"; +import { changeEnterLoading } from "./store/actionCreators"; +import { CSSTransition } from "react-transition-group"; +import SongsList from "../SongList/"; +import MusicNote from "../../baseUI/music-note/index"; function Singer(props) { const initialHeight = useRef(0); @@ -32,71 +32,75 @@ function Singer(props) { const musicNoteRef = useRef(); useEffect(() => { - const id = props.match.params.id; + const id = props.match.params.id; getSingerDataDispatch(id); let h = imageWrapper.current.offsetHeight; initialHeight.current = h; - songScrollWrapper.current.style.top = `${h-OFFSET}px`; + songScrollWrapper.current.style.top = `${h - OFFSET}px`; //把遮罩先放在下面,以裹住歌曲列表 - layer.current.style.top = `${h-OFFSET}px`; + layer.current.style.top = `${h - OFFSET}px`; songScroll.current.refresh(); // eslint-disable-next-line - }, []) + }, []); - const handleScroll = (pos) => { + const handleScroll = pos => { let height = initialHeight.current; const newY = pos.y; const imageDOM = imageWrapper.current; const buttonDOM = collectButton.current; const headerDOM = header.current; const layerDOM = layer.current; - const minScrollY = -(height-OFFSET) + HEADER_HEIGHT; + const minScrollY = -(height - OFFSET) + HEADER_HEIGHT; const percent = Math.abs(newY / height); //说明: 在歌手页的布局中,歌单列表其实是没有自己的背景的,layerDOM其实是起一个遮罩的作用,给歌单内容提供白色背景 //因此在处理的过程中,随着内容的滚动,遮罩也跟着移动 - if(newY > 0) { + if (newY > 0) { //处理往下拉的情况,效果:图片放大,按钮跟着偏移 - imageDOM.style["transform"] = `scale(${1+percent})`; + imageDOM.style["transform"] = `scale(${1 + percent})`; buttonDOM.style["transform"] = `translate3d(0, ${newY}px, 0)`; - layerDOM.style.top = `${height-OFFSET+newY}px`; - } else if(newY >= minScrollY){ + layerDOM.style.top = `${height - OFFSET + newY}px`; + } else if (newY >= minScrollY) { //往上滑动,但是还没超过Header部分 - layerDOM.style.top = `${height-OFFSET-Math.abs(newY)}px`; + layerDOM.style.top = `${height - OFFSET - Math.abs(newY)}px`; layerDOM.style.zIndex = 1; - imageDOM.style.paddingTop = '75%'; + imageDOM.style.paddingTop = "75%"; imageDOM.style.height = 0; imageDOM.style.zIndex = -1; buttonDOM.style["transform"] = `translate3d(0, ${newY}px, 0)`; - buttonDOM.style["opacity"] = `${1-percent*2}`; - } else if(newY < minScrollY){ + buttonDOM.style["opacity"] = `${1 - percent * 2}`; + } else if (newY < minScrollY) { //往上滑动,但是超过Header部分 - layerDOM.style.top = `${HEADER_HEIGHT-OFFSET}px`; + layerDOM.style.top = `${HEADER_HEIGHT - OFFSET}px`; layerDOM.style.zIndex = 1; //防止溢出的歌单内容遮住Header headerDOM.style.zIndex = 100; //此时图片高度与Header一致 - imageDOM.style.height = `${HEADER_HEIGHT}px` + imageDOM.style.height = `${HEADER_HEIGHT}px`; imageDOM.style.paddingTop = 0; imageDOM.style.zIndex = 99; } }; const musicAnimation = (x, y) => { - musicNoteRef.current.startAnimation({x, y}); - } - + musicNoteRef.current.startAnimation({ x, y }); + }; + return ( - props.history.goBack()} > -
setShowStatus(false)} title={artist.name} ref={header}>
+
setShowStatus(false)} + title={artist.name} + ref={header} + >
@@ -111,34 +115,40 @@ function Singer(props) { songs={songs} showCollect={false} usePageSplit={false} - musicAnimation={(x,y) =>musicAnimation(x,y)} - > - + musicAnimation={(x, y) => musicAnimation(x, y)} + > - {loading ? : null} - + {loading ? ( + + + + ) : null} +
- ) + ); } // 映射Redux全局的state到组件的props上 -const mapStateToProps = (state) => ({ - artist: state.getIn(['singerInfo', 'artist']).toJS(), - songs: state.getIn(['singerInfo', 'songsOfArtist']).toJS(), - loading: state.getIn(['singerInfo', 'loading']), - songsCount: state.getIn(['player', 'playList']).size +const mapStateToProps = state => ({ + artist: state.getIn(["singerInfo", "artist"]).toJS(), + songs: state.getIn(["singerInfo", "songsOfArtist"]).toJS(), + loading: state.getIn(["singerInfo", "loading"]), + songsCount: state.getIn(["player", "playList"]).size }); // 映射dispatch到props上 -const mapDispatchToProps = (dispatch) => { +const mapDispatchToProps = dispatch => { return { getSingerDataDispatch(id) { dispatch(changeEnterLoading(true)); dispatch(getSingerInfo(id)); } - } + }; }; // 将ui组件包装成容器组件 -export default connect(mapStateToProps, mapDispatchToProps)(React.memo(Singer)); +export default connect( + mapStateToProps, + mapDispatchToProps +)(React.memo(Singer)); diff --git a/src/application/User/Login/components/LoginForm/index.js b/src/application/User/Login/components/LoginForm/index.js index 3702d6f..7d4d4b5 100644 --- a/src/application/User/Login/components/LoginForm/index.js +++ b/src/application/User/Login/components/LoginForm/index.js @@ -1,13 +1,13 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -import React from "react"; -import { withRouter } from "react-router-dom"; +import React, { forwardRef } from "react"; import { Button, BeautyCheckBox, OtherLoginLink, FormContainer } from "./style"; const noEffect = e => e.preventDefault(); -const LoginForm = ({ history, jumpToLogin }) => { - const jumpToIndex = () => { - history.push("/recommend"); +const LoginForm = forwardRef((props, ref) => { + const { jumpToLogin, setAgreed, jumpToIndex } = props; + const onChangeChecked = e => { + setAgreed(e.target.checked); }; const loginViaThirdApi = () => { alert("第三方登录待开发...."); @@ -26,9 +26,15 @@ const LoginForm = ({ history, jumpToLogin }) => { - +
  • - + 同意{"<<服务条款>>"}和 @@ -55,6 +61,6 @@ const LoginForm = ({ history, jumpToLogin }) => { ); -}; +}); -export default withRouter(LoginForm); +export default LoginForm; diff --git a/src/application/User/Login/components/LoginForm/style.js b/src/application/User/Login/components/LoginForm/style.js index 06cb146..ee7cd8e 100644 --- a/src/application/User/Login/components/LoginForm/style.js +++ b/src/application/User/Login/components/LoginForm/style.js @@ -61,6 +61,216 @@ export const BeautyCheckBox = styled.ul` a { color: #d79088; } + + &.shake-horizontal { + animation-name: shake-horizontal; + animation-duration: 400ms; + animation-timing-function: ease-in-out; + animation-iteration-count: 5s; + animation-play-state: running; + } + @keyframes shake-horizontal { + 2% { + transform: translate(-5px, 0) rotate(0); + } + + 4% { + transform: translate(8px, 0) rotate(0); + } + + 6% { + transform: translate(8px, 0) rotate(0); + } + + 8% { + transform: translate(9px, 0) rotate(0); + } + + 10% { + transform: translate(-7px, 0) rotate(0); + } + + 12% { + transform: translate(1px, 0) rotate(0); + } + + 14% { + transform: translate(-4px, 0) rotate(0); + } + + 16% { + transform: translate(7px, 0) rotate(0); + } + + 18% { + transform: translate(8px, 0) rotate(0); + } + + 20% { + transform: translate(-7px, 0) rotate(0); + } + + 22% { + transform: translate(9px, 0) rotate(0); + } + + 24% { + transform: translate(8px, 0) rotate(0); + } + + 26% { + transform: translate(-2px, 0) rotate(0); + } + + 28% { + transform: translate(5px, 0) rotate(0); + } + + 30% { + transform: translate(6px, 0) rotate(0); + } + + 32% { + transform: translate(4px, 0) rotate(0); + } + + 34% { + transform: translate(3px, 0) rotate(0); + } + + 36% { + transform: translate(7px, 0) rotate(0); + } + + 38% { + transform: translate(-1px, 0) rotate(0); + } + + 40% { + transform: translate(3px, 0) rotate(0); + } + + 42% { + transform: translate(10px, 0) rotate(0); + } + + 44% { + transform: translate(3px, 0) rotate(0); + } + + 46% { + transform: translate(-9px, 0) rotate(0); + } + + 48% { + transform: translate(6px, 0) rotate(0); + } + + 50% { + transform: translate(-8px, 0) rotate(0); + } + + 52% { + transform: translate(6px, 0) rotate(0); + } + + 54% { + transform: translate(1px, 0) rotate(0); + } + + 56% { + transform: translate(5px, 0) rotate(0); + } + + 58% { + transform: translate(-4px, 0) rotate(0); + } + + 60% { + transform: translate(3px, 0) rotate(0); + } + + 62% { + transform: translate(-5px, 0) rotate(0); + } + + 64% { + transform: translate(7px, 0) rotate(0); + } + + 66% { + transform: translate(-8px, 0) rotate(0); + } + + 68% { + transform: translate(-2px, 0) rotate(0); + } + + 70% { + transform: translate(-5px, 0) rotate(0); + } + + 72% { + transform: translate(1px, 0) rotate(0); + } + + 74% { + transform: translate(1px, 0) rotate(0); + } + + 76% { + transform: translate(-9px, 0) rotate(0); + } + + 78% { + transform: translate(6px, 0) rotate(0); + } + + 80% { + transform: translate(8px, 0) rotate(0); + } + + 82% { + transform: translate(10px, 0) rotate(0); + } + + 84% { + transform: translate(-6px, 0) rotate(0); + } + + 86% { + transform: translate(-1px, 0) rotate(0); + } + + 88% { + transform: translate(5px, 0) rotate(0); + } + + 90% { + transform: translate(-1px, 0) rotate(0); + } + + 92% { + transform: translate(7px, 0) rotate(0); + } + + 94% { + transform: translate(-3px, 0) rotate(0); + } + + 96% { + transform: translate(-7px, 0) rotate(0); + } + + 98% { + transform: translate(-4px, 0) rotate(0); + } + + 0%, + 100% { + transform: translate(0, 0) rotate(0); + } + } `; export const OtherLoginLink = styled.div` diff --git a/src/application/User/Login/components/PhoneForm/index.js b/src/application/User/Login/components/PhoneForm/index.js index 2174cdf..6bdd9ec 100644 --- a/src/application/User/Login/components/PhoneForm/index.js +++ b/src/application/User/Login/components/PhoneForm/index.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useCallback } from "react"; import { Header, Container } from "./style"; import { trimPhone } from "../../../../../api/utils"; import StepOne from "./step-one"; @@ -9,12 +9,15 @@ const PhoneForm = props => { const [phone, setPhone] = useState(""); //验证码触发登录操作 - const triggerLogin = vcode => { - loginByVcode(trimPhone(phone), vcode); - }; + const triggerLogin = useCallback( + vcode => { + loginByVcode(trimPhone(phone), vcode); + }, + [phone, loginByVcode] + ); //切换手机号码和验证码表单 - const onClickNext = () => { + const triggerSentVcode = () => { sentVcode(trimPhone(phone)); }; @@ -46,11 +49,15 @@ const PhoneForm = props => { {!sentStatus ? ( ) : ( - + )} ); diff --git a/src/application/User/Login/components/PhoneForm/step-one/index.js b/src/application/User/Login/components/PhoneForm/step-one/index.js index 68d6819..a132a33 100644 --- a/src/application/User/Login/components/PhoneForm/step-one/index.js +++ b/src/application/User/Login/components/PhoneForm/step-one/index.js @@ -6,7 +6,7 @@ const StepOne = props => { const inputRef = useRef(); useEffect(() => { inputRef.current.focus(); - }, []); + }); return ( <>

    diff --git a/src/application/User/Login/components/PhoneForm/step-two/index.js b/src/application/User/Login/components/PhoneForm/step-two/index.js index e00c82c..899d9b6 100644 --- a/src/application/User/Login/components/PhoneForm/step-two/index.js +++ b/src/application/User/Login/components/PhoneForm/step-two/index.js @@ -2,41 +2,45 @@ import React, { useState, useRef, useEffect } from "react"; import { VcodeBox, Container } from "./style"; const maxLength = 4; +const sentPeriod = 60; +let theTimer; const StepTwo = props => { - const { phone, triggerLogin } = props; - const [triggered, setTriggered] = useState(false); + const { phone, triggerLogin, reSentVcode } = props; const [cursorIndex, setCursorIndex] = useState(0); const [vcode, setVcode] = useState(""); - const [timer, setTimer] = useState(60); + const [timer, setTimer] = useState(sentPeriod); const inputRef = useRef(); - useEffect(() => { - let theTimer; - if (!theTimer) { - theTimer = setInterval(() => { - setTimer(timer => timer - 1); - if(timer <= 0) clearTimeout(theTimer); - }, 1000); + inputRef.current.focus(); + if (timer === 0) { + clearInterval(theTimer); + } + if (timer !== sentPeriod) { + return; } - return () => { - clearTimeout(theTimer); - }; + theTimer = setInterval(() => { + setTimer(timer => timer - 1); + }, 1000); }, [timer]); useEffect(() => { - if (vcode.length === 4 && !triggered) { + if (vcode.length === 4) { triggerLogin(vcode); - setTriggered(true); } - }, [vcode, triggerLogin, triggered]); + }, [vcode, triggerLogin]); const onChangeVcode = e => { - if(!e.target.value) return; + if (!e.target.value) return; const val = e.target.value; setVcode(val); setCursorIndex(val.split("").length); }; + const onClickSentVcode = () => { + reSentVcode(); + setTimer(sentPeriod); + }; + return (

      验证码已发送至

    @@ -44,7 +48,13 @@ const StepTwo = props => { {phone.replace(/(\d{3})\s(\d{4})\s(\d{4})/g, "+86 $1 **** $3")} - {timer}S + {timer ? ( + {timer}S + ) : ( + + 重新发送 + + )}

    验证码:

    diff --git a/src/application/User/Login/components/PhoneForm/step-two/style.js b/src/application/User/Login/components/PhoneForm/step-two/style.js index 9450fbd..bf7091a 100644 --- a/src/application/User/Login/components/PhoneForm/step-two/style.js +++ b/src/application/User/Login/components/PhoneForm/step-two/style.js @@ -12,6 +12,9 @@ export const Container = styled.div` padding: 0 25px; margin-top: 10px; justify-content: space-between; + .sentBtn { + color: #0066cc; + } } `; export const VcodeBox = styled.div` @@ -37,7 +40,7 @@ export const VcodeBox = styled.div` .v-code input { position: absolute; top: -100%; - left: -666666px; + left: -100%; opacity: 0; } .v-code .line { diff --git a/src/application/User/Login/index.js b/src/application/User/Login/index.js index 32e3d66..d200d76 100644 --- a/src/application/User/Login/index.js +++ b/src/application/User/Login/index.js @@ -1,5 +1,6 @@ -import React, { useState } from "react"; +import React, { useState, useRef, useEffect } from "react"; import { Container, LogoImg, LogoContainer, LoginContainer } from "./style"; +import { withRouter } from "react-router-dom"; import * as actionCreators from "./store/actionCreators"; import LoginForm from "./components/LoginForm"; import PhoneForm from "./components/PhoneForm"; @@ -8,16 +9,46 @@ import { CSSTransition } from "react-transition-group"; import { connect } from "react-redux"; const Login = props => { - const { LoginByVcodeDispatch, sentVcodeDispatch, sentStatus } = props; + const { + LoginByVcodeDispatch, + sentVcodeDispatch, + sentStatus, + loginStatus, + changeSentStatusDispatch, + history + } = props; const [inPhone, setInPhone] = useState(false); + const [agreed, setAgreed] = useState(false); + const checkBoxRef = useRef(); + + useEffect(() => { + if (loginStatus) { + history.push("/recommend"); + } + }, [loginStatus, history]); + + const jumpToIndex = () => { + history.push("/recommend"); + }; + const jumpToLogin = method => { + if (!agreed) { + // alert("请同意条款"); + checkBoxRef.current.classList.add("shake-horizontal"); + setTimeout(() => { + checkBoxRef.current.classList.remove("shake-horizontal"); + }, 500); + return; + } if (method === "phone") { setInPhone(true); } }; + const onPhoneBack = () => { setInPhone(false); }; + return ( <> @@ -27,10 +58,21 @@ const Login = props => { - + - + changeSentStatusDispatch()} + > { // 映射Redux全局的state到组件的props上 const mapStateToProps = state => ({ userInfo: state.getIn(["user", "userInfo"]), - sentStatus: state.getIn(["user", "sentStatus"]) + sentStatus: state.getIn(["user", "sentStatus"]), + loginStatus: state.getIn(["user", "loginStatus"]) }); // 映射dispatch到props上 const mapDispatchToProps = dispatch => { @@ -61,6 +104,9 @@ const mapDispatchToProps = dispatch => { }, sentVcodeDispatch(phone) { dispatch(actionCreators.sentVcode(phone)); + }, + changeSentStatusDispatch() { + dispatch(actionCreators.saveSentStatus(false)); } }; }; @@ -68,4 +114,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(React.memo(Login)); +)(React.memo(withRouter(Login))); diff --git a/src/application/User/Login/store/actionCreators.js b/src/application/User/Login/store/actionCreators.js index fd56a55..3753f5a 100644 --- a/src/application/User/Login/store/actionCreators.js +++ b/src/application/User/Login/store/actionCreators.js @@ -3,7 +3,11 @@ import { sentVcodeRequest, loginByVcodeRequest } from "../../../../api/request"; -import { CHANGE_USER_INFO, CHANGE_SENT_STATUS, CHANGE_IS_LOGIN } from "./constants"; +import { + CHANGE_USER_INFO, + CHANGE_SENT_STATUS, + CHANGE_LOGIN_STATUS +} from "./constants"; export const saveUserInfo = data => ({ type: CHANGE_USER_INFO, @@ -15,8 +19,8 @@ export const saveSentStatus = data => ({ data }); -export const changeIslogin = data => ({ - type: CHANGE_IS_LOGIN, +export const saveLoginStatus = data => ({ + type: CHANGE_LOGIN_STATUS, data }); @@ -36,7 +40,10 @@ export const loginByVcode = (phone, vcode) => { return dispatch => { loginByVcodeRequest(phone, vcode) .then(res => { - dispatch(saveUserInfo(res)); + if (res.code === 200) { + dispatch(saveUserInfo(res)); + dispatch(saveLoginStatus(true)); + } }) .catch(() => { console.log("登录失败!"); @@ -56,4 +63,4 @@ export const sentVcode = phone => { console.log("请求失败!"); }); }; -}; \ No newline at end of file +}; diff --git a/src/application/User/Login/store/constants.js b/src/application/User/Login/store/constants.js index 15c446f..33e0559 100644 --- a/src/application/User/Login/store/constants.js +++ b/src/application/User/Login/store/constants.js @@ -2,4 +2,4 @@ export const CHANGE_USER_INFO = "user/login/CHANGE_USER_INFO"; export const CHANGE_SENT_STATUS = "user/login/CHANGE_SENT_STATUS"; -export const CHANGE_IS_LOGIN = "user/login/CHANGE_IS_LOGIN"; +export const CHANGE_LOGIN_STATUS = "user/login/CHANGE_LOGIN_STATUS"; diff --git a/src/application/User/Login/store/reducer.js b/src/application/User/Login/store/reducer.js index 34e0e66..693f585 100644 --- a/src/application/User/Login/store/reducer.js +++ b/src/application/User/Login/store/reducer.js @@ -4,7 +4,7 @@ import { fromJS } from "immutable"; const defaultState = fromJS({ userInfo: {}, sentStatus: false, - isLogin: false + loginStatus: false }); export default (state = defaultState, action) => { @@ -13,8 +13,8 @@ export default (state = defaultState, action) => { return state.set("userInfo", action.data); case actionTypes.CHANGE_SENT_STATUS: return state.set("sentStatus", action.data); - case actionTypes.CHANGE_IS_LOGIN: - return state.set("isLogin", action.data); + case actionTypes.CHANGE_LOGIN_STATUS: + return state.set("loginStatus", action.data); default: return state; }