├── global.d.ts ├── .prettierignore ├── script ├── build.js └── dev.js ├── test ├── style │ ├── index.css │ └── index.scss ├── store.tsx ├── assets │ └── style │ │ └── px2rem.scss ├── IntegerStep.tsx ├── store │ └── public.tsx └── index.tsx ├── public ├── favicon.ico ├── manifest.json └── cloudmusic.js ├── src ├── views │ ├── PgWrong │ │ └── index.tsx │ ├── PgPlayDetails │ │ └── store.ts │ ├── PgIndex │ │ ├── store.ts │ │ └── index.tsx │ ├── PgAbout │ │ ├── index.scss │ │ └── index.tsx │ ├── PgLeftSlider │ │ ├── index.scss │ │ └── index.tsx │ ├── PgRecommendedDaily │ │ ├── index.scss │ │ └── index.tsx │ ├── PgLogin │ │ ├── PgPasswordInput │ │ │ ├── store.ts │ │ │ ├── index.tsx │ │ │ └── index.scss │ │ └── PgPhoneInput │ │ │ ├── index.scss │ │ │ └── index.tsx │ ├── PgHome │ │ ├── index.tsx │ │ └── index.scss │ ├── PgFind │ │ └── store.ts │ ├── PgFirends │ │ ├── index.tsx │ │ └── index.scss │ ├── PgRankingList │ │ ├── index.tsx │ │ ├── index.scss │ │ └── singleRowData.ts │ ├── PgRadioList │ │ ├── index.scss │ │ └── index.tsx │ ├── PgVidio │ │ ├── index.scss │ │ └── index.tsx │ ├── PgMy │ │ └── index.scss │ ├── PgSongSquare │ │ └── index.scss │ ├── PgMvDetails │ │ ├── index.scss │ │ └── index.tsx │ ├── PgYunCun │ │ ├── index.tsx │ │ └── index.scss │ └── PgSearch │ │ └── index.scss ├── assets │ ├── images │ │ ├── logo.png │ │ ├── timg.gif │ │ ├── loding.png │ │ └── music.gif │ └── style │ │ └── px2rem.scss ├── store │ ├── index.tsx │ └── modules │ │ ├── commonStore.ts │ │ └── playerStore.ts ├── components │ ├── Icons │ │ └── index.tsx │ ├── index.ts │ ├── Bubbleflow │ │ ├── index.scss │ │ └── index.tsx │ ├── GloblePlayer │ │ ├── index.scss │ │ └── index.tsx │ ├── Skeleton │ │ ├── index.scss │ │ └── index.tsx │ ├── Toast │ │ ├── index.tsx │ │ └── index.scss │ ├── Headers │ │ ├── index.scss │ │ └── index.tsx │ └── PlayerModule │ │ └── index.tsx ├── App.test.js ├── index.css ├── utils │ ├── useQuery.ts │ ├── formatSeconds.ts │ └── fetch.ts ├── index.tsx ├── router │ ├── routerView.tsx │ └── index.ts ├── App.tsx ├── App.css └── api │ └── index.ts ├── images.d.ts ├── .gitignore ├── web.config.ts ├── tsconfig.json ├── README.md ├── tsconfig.md ├── config ├── webpack.config.dev.ts ├── webpack.config.test.ts ├── webpack.config.common.ts └── webpack.config.prod.ts └── package.json /global.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.ts 3 | -------------------------------------------------------------------------------- /script/build.js: -------------------------------------------------------------------------------- 1 | require('../config/webpack.config.prod') -------------------------------------------------------------------------------- /script/dev.js: -------------------------------------------------------------------------------- 1 | require('../config/webpack.config.dev.js') -------------------------------------------------------------------------------- /test/style/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: aqua; 3 | display: flex; 4 | transform: all 1s; 5 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ignorance-of-Dong/optimization_open_neteasy_cloud/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/views/PgWrong/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export default function Wrong(): JSX.Element {return
404
} 3 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ignorance-of-Dong/optimization_open_neteasy_cloud/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/timg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ignorance-of-Dong/optimization_open_neteasy_cloud/HEAD/src/assets/images/timg.gif -------------------------------------------------------------------------------- /test/store.tsx: -------------------------------------------------------------------------------- 1 | import { observable } from "mobx"; 2 | class Stores{ 3 | @observable initn = '2222' 4 | } 5 | 6 | export default Stores -------------------------------------------------------------------------------- /src/assets/images/loding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ignorance-of-Dong/optimization_open_neteasy_cloud/HEAD/src/assets/images/loding.png -------------------------------------------------------------------------------- /src/assets/images/music.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ignorance-of-Dong/optimization_open_neteasy_cloud/HEAD/src/assets/images/music.gif -------------------------------------------------------------------------------- /test/style/index.scss: -------------------------------------------------------------------------------- 1 | @import '../assets/style/px2rem.scss'; 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | font-size: px2rem(14); 6 | } -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module '*.svg' 3 | declare module '*.png' 4 | declare module '*.jpg' 5 | declare module '*.jpeg' 6 | declare module '*.gif' 7 | declare module '*.bmp' 8 | declare module '*.tiff' 9 | -------------------------------------------------------------------------------- /src/store/index.tsx: -------------------------------------------------------------------------------- 1 | import commonStore from "./modules/commonStore" 2 | import playerStore from "./modules/playerStore" 3 | 4 | const Store = { 5 | commonStore, 6 | playerStore 7 | } 8 | export default Store; -------------------------------------------------------------------------------- /src/components/Icons/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Icon(props: any) { 4 | return {props.un} 5 | } 6 | 7 | export default Icon -------------------------------------------------------------------------------- /src/views/PgPlayDetails/store.ts: -------------------------------------------------------------------------------- 1 | import {observable, action} from 'mobx' 2 | class Track{ 3 | @observable trackId = "" 4 | 5 | @action.bound 6 | async localTrackId(id) { 7 | this.trackId = id; 8 | } 9 | } 10 | 11 | export default new Track() -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/views/PgIndex/store.ts: -------------------------------------------------------------------------------- 1 | import {observable, action} from 'mobx' 2 | 3 | class Index{ 4 | @observable heightlight = 1 5 | @observable open = false 6 | 7 | @action.bound 8 | changSHstate(flag) { 9 | console.log(flag) 10 | this.open = flag 11 | } 12 | } 13 | 14 | export default new Index() -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/views/PgAbout/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .about { 3 | width: 100%; 4 | height: 100%; 5 | background: #ccc; 6 | .map { 7 | width: 95%; 8 | height: px2rem(1500); 9 | // height: 100%; 10 | background: #ccc; 11 | margin: 0 auto; 12 | border-radius: px2rem(10); 13 | overflow: hidden; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/views/PgLeftSlider/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .left-silder-signature{ 3 | width: px2rem(400); 4 | font-size: px2rem(35); 5 | display: block; 6 | position: absolute; 7 | top: px2rem(100); 8 | right: px2rem(120); 9 | overflow: hidden;/*超出部分隐藏*/ 10 | text-overflow: ellipsis;/* 超出部分显示省略号 */ 11 | white-space: nowrap;/*规定段落中的文本不进行换行 */ 12 | } -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | import Headers from './Headers' 2 | 3 | import Toasts, { ToastLoding } from './Toast' 4 | 5 | import Icons from './Icons' 6 | 7 | import Bubbleflow from './Bubbleflow' 8 | 9 | import Skeleton from './Skeleton' 10 | 11 | let ToastLodingPro = new ToastLoding() 12 | export { 13 | ToastLodingPro, 14 | Bubbleflow, 15 | Skeleton, 16 | Headers, 17 | Toasts, 18 | Icons 19 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/useQuery.ts: -------------------------------------------------------------------------------- 1 | 2 | function query(): any { 3 | let url = window.location.href 4 | if (url.indexOf('?') === -1) return null 5 | var arr1 = url.split("?"); 6 | var params = arr1[1].split("&"); 7 | var obj = {};//声明对象 8 | for (var i = 0; i < params.length; i++) { 9 | var param = params[i].split("="); 10 | obj[param[0]] = decodeURIComponent(param[1]);//为对象赋值 11 | } 12 | return obj; 13 | } 14 | 15 | export default query -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | ./src/web.config.json 5 | .vscode 6 | /tests/e2e/videos/ 7 | /tests/e2e/screenshots/ 8 | .cache-loader 9 | .awcache 10 | .env 11 | 12 | # local env files 13 | .env.local 14 | .env.*.local 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # Editor directories and files 22 | .idea 23 | .vscode 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw* 29 | 30 | /dist 31 | /src/web.config.json 32 | /public/appConfig.json 33 | 34 | *.zip 35 | 36 | /dist 37 | /build 38 | /music -------------------------------------------------------------------------------- /src/assets/style/px2rem.scss: -------------------------------------------------------------------------------- 1 | $designWidth: 375; 2 | 3 | @function px2rem($px:18) { 4 | @return $px*10/$designWidth + rem; 5 | } 6 | 7 | @mixin placeholder($placeholderColor) { 8 | &::-webkit-input-placeholder { /* WebKit browsers */ 9 | color: $placeholderColor; 10 | } 11 | &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ 12 | color: $placeholderColor; 13 | opacity: 1; 14 | } 15 | &::-moz-placeholder { /* Mozilla Firefox 19+ */ 16 | color: $placeholderColor; 17 | opacity: 1; 18 | } 19 | &:-ms-input-placeholder { /* Internet Explorer 10+ */ 20 | color: $placeholderColor; 21 | } 22 | } -------------------------------------------------------------------------------- /test/assets/style/px2rem.scss: -------------------------------------------------------------------------------- 1 | $designWidth: 375; 2 | 3 | @function px2rem($px:18) { 4 | @return $px*10/$designWidth + rem; 5 | } 6 | 7 | @mixin placeholder($placeholderColor) { 8 | &::-webkit-input-placeholder { /* WebKit browsers */ 9 | color: $placeholderColor; 10 | } 11 | &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ 12 | color: $placeholderColor; 13 | opacity: 1; 14 | } 15 | &::-moz-placeholder { /* Mozilla Firefox 19+ */ 16 | color: $placeholderColor; 17 | opacity: 1; 18 | } 19 | &:-ms-input-placeholder { /* Internet Explorer 10+ */ 20 | color: $placeholderColor; 21 | } 22 | } -------------------------------------------------------------------------------- /src/utils/formatSeconds.ts: -------------------------------------------------------------------------------- 1 | function formatSeconds(times): string { 2 | var result = '00:00'; 3 | var hour, minute, second 4 | if (times > 0) { 5 | hour = Math.floor(times / 3600); 6 | if (hour < 10) { 7 | hour = "0" + hour; 8 | } 9 | minute = Math.floor((times - 3600 * hour) / 60); 10 | if (minute < 10) { 11 | minute = "0" + minute; 12 | } 13 | 14 | second = Math.floor((times - 3600 * hour - 60 * minute) % 60); 15 | if (second < 10) { 16 | second = "0" + second; 17 | } 18 | result = minute + ':' + second; 19 | } 20 | return result; 21 | } 22 | export default formatSeconds -------------------------------------------------------------------------------- /src/views/PgRecommendedDaily/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/views/PgPlayDetails/index.scss'; 2 | .serial-numbers { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | span{ 7 | display: block; 8 | width: px2rem(90); 9 | height: px2rem(90); 10 | border-radius: px2rem(15); 11 | overflow: hidden; 12 | img{ 13 | width: 100%; 14 | height: 100%; 15 | } 16 | } 17 | } 18 | .start-play-all-boxshaw { 19 | box-shadow: -4px -7px 5px #fff; 20 | } 21 | .serial-content-song-names { 22 | width: px2rem(600); 23 | overflow: hidden; 24 | white-space: nowrap; 25 | text-overflow: ellipsis; 26 | overflow: hidden; 27 | } 28 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-03 15:28:04 6 | * @Descripttion: 7 | */ 8 | import React from 'react'; 9 | import ReactDOM from 'react-dom'; 10 | import './index.css'; 11 | import App from './App'; 12 | import { Provider } from "mobx-react"; 13 | import Store from './store/index' 14 | 15 | console.log({...Store}) 16 | 17 | ReactDOM.render(, document.getElementById('root')); 18 | 19 | // If you want your app to work offline and load faster, you can change 20 | // unregister() to register() below. Note this comes with some pitfalls. 21 | // Learn more about service workers: https://bit.ly/CRA-PWA 22 | -------------------------------------------------------------------------------- /src/views/PgLogin/PgPasswordInput/store.ts: -------------------------------------------------------------------------------- 1 | import { observable, action, flow } from 'mobx' 2 | import { apilogincellphone } from 'api' 3 | import query from 'utils/useQuery' 4 | class PgPassword{ 5 | 6 | 7 | @observable password = "" 8 | 9 | // 输入密码【修改值】 10 | @action.bound 11 | changePass(val) { 12 | this.password = val 13 | } 14 | 15 | // 登陆 16 | @action.bound 17 | passwordFn = flow(function * () { 18 | let { phone } = query() 19 | 20 | let params = { 21 | phone: phone, 22 | password: this.password 23 | } 24 | yield apilogincellphone(params).then(res => { 25 | sessionStorage.setItem('useMsg', JSON.stringify(res.profile)) 26 | sessionStorage.setItem('tabId', '1') 27 | }) 28 | }) 29 | } 30 | 31 | export default new PgPassword() -------------------------------------------------------------------------------- /test/IntegerStep.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import {observer, inject} from 'mobx-react' 3 | import Stores from "./store"; 4 | // @inject('Pubcli') 5 | // @observer 6 | // class IntegerStep extends React.Component { 7 | // Stores = Stores 8 | // render() { 9 | // console.log(this.props, 'Stores') 10 | // let { changValue, total } = this.props.Pubcli 11 | // return <> 12 | //
{total}
13 | 14 | // 17 | 18 | // 19 | // } 20 | // } 21 | 22 | const IntegerStep = inject('Pubcli')(observer((props) => { 23 | let { changValue, total } = props.Pubcli 24 | console.log(props) 25 | return <> 26 |
{total}
27 | 28 | 31 | 32 | 33 | })) 34 | export default IntegerStep -------------------------------------------------------------------------------- /src/views/PgHome/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './index.scss' 3 | import logo from 'assets/images/logo.png' 4 | function Home(props: any): JSX.Element { 5 | return ( 6 | <> 7 |
8 |
9 | 10 |
11 |
12 |
{ 13 | props.history.push('/loginphone') 14 | }}>手机号登陆
15 |
16 |
{ 17 | props.history.push('/index/fined') 18 | }}> 19 | 立即体验 20 |
21 |
22 |
23 |
24 | 25 | ) 26 | } 27 | 28 | export default Home -------------------------------------------------------------------------------- /src/views/PgFind/store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-10-25 18:18:36 6 | * @Descripttion: 7 | */ 8 | import {observable, action} from 'mobx' 9 | import { Icons, Toasts } from 'components/index' 10 | import { apipersonalizedSongList, apialbum, apifirstMv, apibanner } from 'api' 11 | class Find{ 12 | @observable recommendedSongList = [] 13 | @observable newDish = [] 14 | @observable personalizedMv = [] 15 | @observable _condition = [] 16 | @observable _banner = [] 17 | 18 | @action.bound 19 | async getapipersonalizedSongList() { 20 | let params = { 21 | limit: 6 22 | } 23 | try { 24 | await apipersonalizedSongList(params).then((res: any) => { 25 | this.recommendedSongList = res.result 26 | }) 27 | await apibanner().then(res => { 28 | this._banner = res.banners 29 | }) 30 | } catch (err) { 31 | console.log(err) 32 | Toasts('网络请求异常,请两分钟后再试', 2000) 33 | } 34 | } 35 | } 36 | 37 | export default new Find() -------------------------------------------------------------------------------- /src/store/modules/commonStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-03 15:09:36 6 | * @Descripttion: 7 | */ 8 | import { observable, action } from 'mobx' 9 | 10 | class Index { 11 | @observable songListDetails: Array = [] 12 | @observable tabBarHeight: number = 0 13 | @observable color: string = '#ccc' 14 | @observable trackId: string = "" 15 | 16 | @observable trackDetail = null; 17 | @observable trackList = [] 18 | 19 | @action.bound 20 | getSongListDetails(data): void { 21 | this.songListDetails = data 22 | } 23 | 24 | @action.bound 25 | setTabBarHeight(hei): void { 26 | this.tabBarHeight = hei 27 | } 28 | 29 | @action.bound 30 | setShow():void { 31 | this.color = 'red' 32 | } 33 | 34 | @action.bound 35 | async localTrackId(id) { 36 | this.trackId = id; 37 | } 38 | 39 | @action.bound 40 | setTrackDetail(detail) { 41 | this.trackDetail = detail; 42 | } 43 | 44 | @action.bound 45 | setTrackList(detail) { 46 | this.trackList = detail; 47 | } 48 | 49 | } 50 | 51 | export default new Index() -------------------------------------------------------------------------------- /src/components/Bubbleflow/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .bubble-wrap { 4 | width: 100%; 5 | // height: 100%; 6 | // background: Red; 7 | position: relative; 8 | display: flex; 9 | justify-content: flex-start; 10 | flex-wrap: wrap; 11 | .bubble-tip { 12 | margin: px2rem(20); 13 | padding: px2rem(15) px2rem(30); 14 | border-radius: px2rem(30); 15 | background: #ccc; 16 | transform-origin: center; 17 | opacity: .5; 18 | // transform: rotateZ(2deg) 19 | // animation: Shake .5s infinite ; 20 | 21 | } 22 | @keyframes Shake { 23 | 0% { 24 | transform: translateX(0) translateY(0) rotateZ(0) scale(1); 25 | } 26 | 25%{ 27 | transform: translateX(2px) translateY(2px) rotateZ(1deg) scale(0.5); 28 | } 29 | 50% { 30 | transform: translateX(-2px) translateY(-2px) rotateZ(-2deg) scale(0.7); 31 | } 32 | 75% { 33 | transform: translateX(2px) translateY(2px) rotateZ(1deg) scale(0.9); 34 | } 35 | 100% { 36 | transform: translateX(0) translateY(0) rotateZ(0) scale(1); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/components/GloblePlayer/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/style/px2rem.scss'; 2 | // #root { 3 | // padding-bottom: px2rem(100); 4 | // } 5 | .playerGloble { 6 | position: absolute; 7 | bottom: 0; 8 | left: 0; 9 | height: px2rem(100); 10 | width: 100%; 11 | background-color: #fff; 12 | z-index: 99999; 13 | box-shadow: 0px 0px 20px 2px #ccc; 14 | display: flex; 15 | align-items: center; 16 | padding: 0 px2rem(20); 17 | } 18 | .playerGloble .cover { 19 | width: px2rem(70); 20 | height: px2rem(70); 21 | border-radius: 50%; 22 | border-radius: 50%; 23 | overflow: hidden; 24 | img { 25 | width: 100%; 26 | height: 100%; 27 | } 28 | } 29 | .playerGloble .musicName { 30 | flex: 1; 31 | padding:0 px2rem(20); 32 | overflow: hidden; 33 | white-space: nowrap; 34 | text-overflow: ellipsis; 35 | } 36 | .playerGloble .control .player-icon { 37 | // width: px2rem(70); 38 | // height: px2rem(70); 39 | // border-radius: 50%; 40 | // background-color: aqua; 41 | font-size: 28px; 42 | } 43 | .playerGloble .playerList { 44 | width: px2rem(70); 45 | height: px2rem(70); 46 | border-radius: 50%; 47 | background-color: aqua; 48 | margin-left: px2rem(20); 49 | } -------------------------------------------------------------------------------- /src/components/Skeleton/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .slider-list { 3 | height: px2rem(350) !important; 4 | border-radius: px2rem(20); 5 | } 6 | 7 | .slider-frame { 8 | border-radius: px2rem(10); 9 | } 10 | 11 | .slider-slide { 12 | height: 100% !important; 13 | } 14 | .skeleton-banner { 15 | width: 100%; 16 | } 17 | .video-skeleton { 18 | width: 95%; 19 | margin: 0 auto; 20 | height: px2rem(500); 21 | .vs-tip { 22 | width: 70%; 23 | height: px2rem(40); 24 | background: #ccc; 25 | margin: px2rem(20); 26 | overflow: hidden; 27 | background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%); 28 | background-size: 400% 100%; 29 | animation: ant-skeleton-loading 1.4s ease infinite; 30 | } 31 | .vs-tip-long { 32 | width: 90%; 33 | height: px2rem(40); 34 | background: #ccc; 35 | margin: px2rem(20); 36 | background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%); 37 | background-size: 400% 100%; 38 | animation: ant-skeleton-loading 1.4s ease infinite; 39 | 40 | } 41 | @keyframes ant-skeleton-loading { 42 | 0% { 43 | background-position: 100% 50%; 44 | } 45 | 46 | 100% { 47 | background-position: 0 50%; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/components/Toast/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss' 4 | import lodImage from 'assets/images/loding.png' 5 | function Toast(props: any): JSX.Element { 6 | return ( 7 | <> 8 |
{props.value}
9 | 10 | ) 11 | } 12 | 13 | function CToast(value: string, time: number): void { 14 | let div = document.createElement('div'); 15 | document.body.appendChild(div); 16 | ReactDOM.render(, div) 17 | setTimeout(() => { 18 | document.body.removeChild(div); 19 | }, time) 20 | } 21 | 22 | function LodingToast(props: any): JSX.Element { 23 | return <> 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 | 33 | } 34 | 35 | export class ToastLoding { 36 | 37 | dom = null 38 | 39 | loading(): void { 40 | this.dom = document.createElement('div'); 41 | document.body.appendChild(this.dom); 42 | ReactDOM.render(, this.dom) 43 | } 44 | 45 | hide(): void { 46 | document.body.removeChild(this.dom); 47 | } 48 | } 49 | 50 | export default CToast -------------------------------------------------------------------------------- /src/components/Headers/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | // .header-bar { 4 | // width: 100%; 5 | // height: px2rem(100); 6 | // // border-bottom: px2rem(1) solid #ccc; 7 | // display: flex; 8 | // justify-content: flex-start; 9 | // align-items: center; 10 | // box-sizing: border-box; 11 | // padding: px2rem(20); 12 | 13 | // } 14 | .back-header { 15 | margin-right: px2rem(20); 16 | } 17 | 18 | .text-header { 19 | width: px2rem(600); 20 | overflow: hidden; 21 | white-space: nowrap; 22 | text-overflow: ellipsis; 23 | font-size: px2rem(35); 24 | } 25 | 26 | .header-icon { 27 | display: flex; 28 | justify-content: center; 29 | align-items: center; 30 | } 31 | 32 | @media screen and (min-width:430px) { 33 | .header-bar { 34 | width: 430px; 35 | height: px2rem(100); 36 | // border-bottom: px2rem(1) solid #ccc; 37 | display: flex; 38 | justify-content: flex-start; 39 | align-items: center; 40 | box-sizing: border-box; 41 | padding: px2rem(20); 42 | } 43 | } 44 | 45 | @media screen and (max-width:430px) { 46 | 47 | .header-bar { 48 | width: 100%; 49 | height: px2rem(100); 50 | // border-bottom: px2rem(1) solid #ccc; 51 | display: flex; 52 | justify-content: flex-start; 53 | align-items: center; 54 | box-sizing: border-box; 55 | padding: px2rem(20); 56 | } 57 | } -------------------------------------------------------------------------------- /src/components/Bubbleflow/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.scss' 3 | const hotguide: Array = [ 4 | '好好生活 慢慢相遇', 5 | '抱抱', 6 | '『我希望正在看评论的人永远开心』⠀', 7 | '你不是一个人', 8 | '下山一定要记得买菜,生快', 9 | '千年修行,只为再见', 10 | '仅一夜之间,我的心判若两人', 11 | ' 陌生人,加油!', 12 | '我愿陪你寻得黑暗尽头的光', 13 | '快乐这东西,能偷一点是一点', 14 | '我委婉的想你了', 15 | '我爱你可不止三千遍', 16 | 'I just wanna be your Mr. Right in your real life', 17 | '今年冬天要更努力了' 18 | ] 19 | function Bubbleflow(): JSX.Element { 20 | const bacCol: Array = ['#1abc9c', '#2ecc71', '#3498db', '#9b59b6', '#34495e', '#16a085', '#27ae60', '#2980b9', '#8e44ad', '#2c3e50', '#f1c40f', '#e67e22', '#e74c3c', '#ecf0f1', '#95a5a6', '#f39c12', '#d35400', '#c0392b', '#bdc3c7', '#7f8c8d'] 21 | 22 | return <> 23 |
24 | { 25 | hotguide.map((item, index) => { 26 | return ( 27 |
32 | {item} 33 |
34 | ) 35 | }) 36 | } 37 |
38 | 39 | } 40 | 41 | export default Bubbleflow -------------------------------------------------------------------------------- /src/views/PgLogin/PgPhoneInput/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .phone-wrap{ 3 | width: 100%; 4 | height: px2rem(300); 5 | // background: #ccc; 6 | .phone-tips{ 7 | width: 100%; 8 | font-size: px2rem(35); 9 | padding:px2rem(60) px2rem(30); 10 | box-sizing: border-box; 11 | color: #ccc; 12 | } 13 | .phone-input{ 14 | width: 100%; 15 | height: px2rem(80); 16 | padding:0 px2rem(30); 17 | box-sizing: border-box; 18 | .phone-border{ 19 | width: 100%; 20 | height: 100%; 21 | border-bottom:px2rem(1) solid #ccc; 22 | display: flex; 23 | justify-content: space-between; 24 | align-items: center; 25 | } 26 | } 27 | .phone-text-wrap { 28 | flex:1; 29 | padding:0 px2rem(30); 30 | .phone-text{ 31 | width: 100%; 32 | height: 100%; 33 | background: transparent; 34 | border:0; 35 | } 36 | } 37 | .phone-buttom{ 38 | width: 90%; 39 | height: px2rem(95); 40 | background: #f33838; 41 | margin: 0 auto; 42 | margin-top: px2rem(60); 43 | border-radius: px2rem(50); 44 | text-align: center; 45 | line-height: px2rem(95); 46 | font-size: px2rem(35); 47 | color: #fff; 48 | 49 | &:active { 50 | background: #e07e7e; 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/views/PgLogin/PgPasswordInput/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { observer } from 'mobx-react' 3 | import Store from './store' 4 | 5 | import Headers from 'components/Headers' 6 | import './index.scss' 7 | 8 | function PgPasswordInput(props: any): JSX.Element { 9 | let { passwordFn, changePass, password } = Store 10 | 11 | async function toLogin() { 12 | await passwordFn() 13 | props.history.push('/index/fined') 14 | } 15 | 16 | return ( 17 | <> 18 | 19 |
20 |
21 |
22 |
23 | { 24 | changePass(e.target.value) 25 | }} /> 26 |
27 |
28 | 忘记密码? 29 |
30 |
31 |
32 |
{ 33 | toLogin() 34 | }}> 35 | 登陆 36 |
37 |
38 | 39 | ) 40 | } 41 | 42 | export default observer(PgPasswordInput) -------------------------------------------------------------------------------- /src/router/routerView.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-16 17:38:27 6 | * @Descripttion: 7 | */ 8 | import React, { Suspense } from 'react'; 9 | import { HashRouter, Switch, Route, Redirect } from 'react-router-dom' 10 | // import Wrong from '../views/PgWrong'//404页面 11 | function RouterView(props: any) { 12 | let routers = props.routers ? props.routers : props.routerList 13 | // const { route } = this.props; 14 | const defaultRouter = { 15 | return 16 | }} key={22} exact/> 17 | return ( 18 | 19 | }> 20 | 21 | { 22 | routers.map((item, index) => { 23 | const Comp = item.component 24 | return { 25 | console.log(item.meta) 26 | sessionStorage.setItem("meta", JSON.stringify(item.meta)) 27 | return 28 | }} key={index}/> 29 | }).concat(defaultRouter) 30 | } 31 | 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | 39 | 40 | export default RouterView -------------------------------------------------------------------------------- /src/components/Headers/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, useImperativeHandle, useRef, Ref } from 'react' 2 | import './index.scss' 3 | 4 | interface props{ 5 | ref?: any, 6 | otherEl?: JSX.Element, 7 | className?: String, 8 | props: any, 9 | children?: any, 10 | style?: any, 11 | svgCol?: string 12 | } 13 | function Headers(props: props, ref: Ref) { 14 | const headerRef = useRef() 15 | useImperativeHandle(ref, () => { 16 | return { 17 | headerRef: (headerRef as Ref) 18 | } 19 | }) 20 | let { svgCol = '#000'} = props 21 | return ( 22 | <> 23 |
24 | {props.otherEl} 25 |
26 | { props.props.history.go(-1)}}> 27 | 28 | 29 |
30 |
31 | {props.children || '手机号登陆'} 32 |
33 |
34 | 35 | ) 36 | } 37 | 38 | export default forwardRef(Headers) -------------------------------------------------------------------------------- /web.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-07-17 10:10:00 4 | * @LastEditors: OBKoro1 5 | * @LastEditTime: 2019-07-29 14:56:07 6 | * @Description: webpack配置文件 7 | */ 8 | const path = require('path') 9 | const interfaces = require('os').networkInterfaces(); // 在开发环境中获取局域网中的本机iP地址 10 | var IPAdress: String = ''; 11 | for (var devName in interfaces) { 12 | var iface = interfaces[devName]; 13 | for (var i = 0; i < iface.length; i++) { 14 | var alias = iface[i]; 15 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 16 | IPAdress = alias.address; 17 | } 18 | } 19 | } 20 | interface overalSituationConfig{ 21 | Serverport: String, 22 | IPv4: String, 23 | DevelopmentModel: String, 24 | ComponentLibrary: String, 25 | Vendor: Array, 26 | ResolveAlias: Object 27 | } 28 | 29 | let Config: overalSituationConfig = { 30 | Serverport: "4000", // 端口 31 | IPv4: IPAdress, // 本地IP地址 32 | DevelopmentModel: 'h5', // 开发类型 h5 || pc 【没用!!】 33 | ComponentLibrary: 'antd-mobile', // 使用的UI组件 antd-mobile || antd 34 | Vendor: ['react'], // 对第三方包分包配置 35 | ResolveAlias: { 36 | "src": path.resolve(process.cwd(), 'src'), // 配置别名 37 | "components": path.resolve(process.cwd(), 'src', 'components'), 38 | "utils": path.resolve(process.cwd(), 'src', 'utils'), 39 | "views": path.resolve(process.cwd(), 'src', 'views'), 40 | "api": path.resolve(process.cwd(), 'src', 'api'), 41 | "assets": path.resolve(process.cwd(), 'src', 'assets'), 42 | } 43 | } 44 | export default Config 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths":{ 5 | "components/*": ["src/components/*"], 6 | "utils/*": ["src/utils/*"], 7 | "views/*": ["src/views/*"], 8 | "assets/*": ["src/assets/*"], 9 | "api": ["src/api"] 10 | }, 11 | "outDir": "./dist/", 12 | "sourceMap": false, 13 | "module": "commonjs", 14 | "target": "es5", 15 | "lib": ["es6", "esnext", "dom", "es2015"], 16 | "jsx": "react", 17 | "strict": false, 18 | "noImplicitThis": false, // 当 this表达式的值为 any类型的时候,生成一个错误。 19 | "noFallthroughCasesInSwitch": true, //报告switch语句的fallthrough错误。(即,不允许switch的case语句贯穿) 20 | "allowUnreachableCode": true, // 不报告执行不到的代码错误。 21 | "allowUnusedLabels": true, // 不报告未使用的标签错误。 22 | "removeComments": true, // 删除所有注释,除了以 /!*开头的版权信息 23 | "emitDecoratorMetadata": true, // 给源码里的装饰器声明加上设计类型元数据。查看 issue #2577了解更多信息。 24 | "experimentalDecorators": true, // 启用实验性的ES装饰器。 25 | "downlevelIteration": true, 26 | "moduleResolution": "node", // 决定如何处理模块。或者是"Node"对于Node.js/io.js,或者是"Classic"(默认)。查看模块解析了解详情。 27 | "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。 28 | "esModuleInterop": true, // 同上 29 | "reactNamespace" : "React", // 当目标为生成 "react" JSX时,指定 createElement和 __spread的调用对象 30 | "allowJs": true, //是否允许工程中js和ts同时存在。 31 | "skipLibCheck": false, // 跳过lib文件的静态检查,哎,不是所有的lib都给你写得规规整整的。 32 | }, 33 | "include": [ 34 | "./src/**/*", 35 | "images.d.ts" 36 | ], 37 | "exclude": [ 38 | "node_modules", 39 | "**/*.spec.ts" 40 | ] 41 | } -------------------------------------------------------------------------------- /test/store/public.tsx: -------------------------------------------------------------------------------- 1 | import { observable, configure, runInAction, action, reaction, computed, autorun, when } from "mobx"; 2 | 3 | 4 | // configure({ enforceActions: 'always' }) // 强制进行action 5 | let obj = { 6 | a: 111 7 | } 8 | 9 | 10 | 11 | class Pubcli { 12 | 13 | constructor () { 14 | // this.disposer() 15 | when( 16 | () => this.total > 20, 17 | () => this.init() 18 | ) 19 | } 20 | 21 | @observable todos = [ 22 | { 23 | title: "Make coffee", 24 | done: true, 25 | }, 26 | { 27 | title: "Find biscuit", 28 | done: false 29 | } 30 | ] 31 | @observable youses = 'ture' 32 | 33 | reaction2 = reaction( 34 | () => this.todos.map(todo => todo.title), 35 | titles => console.log("reaction 2:", titles.join(", ")) 36 | ); 37 | 38 | @observable price = 1 39 | 40 | count = 1 41 | // @action.bound 42 | changValue = () => { 43 | // console.log(observable.map(obj).has('b')) 44 | // console.log() 45 | this.todos = [ 46 | ...this.todos, 47 | { 48 | title: "Find biscuit", 49 | done: false 50 | } 51 | ] 52 | this.price = this.price + 1 53 | 54 | } 55 | disposer = autorun(() => { 56 | console.log(this.price, 'this.price') 57 | }); 58 | @computed get total() { 59 | return this.price * 10 60 | } 61 | 62 | init() { 63 | console.log(this.count++, '否者') 64 | } 65 | 66 | } 67 | 68 | export default new Pubcli() -------------------------------------------------------------------------------- /test/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-07-16 09:54:09 4 | * @LastEditors: OBKoro1 5 | * @LastEditTime: 2019-07-29 15:29:19 6 | * @Description: 测试高级语法 7 | */ 8 | import './style/index.css' 9 | import './style/index.scss' 10 | import * as ReactDOM from 'react-dom'; 11 | import * as React from 'react' 12 | import IntegerStep from './IntegerStep' 13 | import { Provider } from "mobx-react"; 14 | import Pubcli from './store/public' 15 | 16 | 17 | 18 | let foo = () => 1; 19 | 20 | let obj = { 21 | a: 2, 22 | b: 3 23 | } 24 | let { 25 | a 26 | } = obj 27 | let objs = { 28 | ...obj, 29 | f: 4 30 | } 31 | console.log(foo()) 32 | 33 | class Name { 34 | state: { 35 | a: 1 36 | } 37 | suert() { 38 | return 1123599999 39 | } 40 | } 41 | console.log(new Name().suert()) 42 | console.log(React) 43 | // const element =

Hello, world!

; 44 | 45 | 46 | function fn() { 47 | return new Promise((resolve,reject) => { 48 | setTimeout(() => { 49 | console.log('加载promise') 50 | }, 1000) 51 | }) 52 | } 53 | fn() 54 | 55 | 56 | let arr = [1,2,3,5,4,5,8,9,8,100] 57 | 58 | new Set(arr) 59 | 60 | console.log(arr) 61 | 62 | 63 | function* helloWorldGenerator() { 64 | yield 'hello'; 65 | yield 'world'; 66 | return 'ending'; 67 | } 68 | 69 | var hw = helloWorldGenerator(); 70 | 71 | 72 | hw.next() 73 | @testable 74 | class MyClass { 75 | static isTestable: any; 76 | }; 77 | 78 | function testable(target: any) { 79 | target.isTestable = true; 80 | } 81 | console.log(MyClass.isTestable, 'ss') 82 | 83 | ReactDOM.render( 84 | 85 | 86 | 87 | , document.getElementById("root")); 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![optimization_open_neteasy_cloud](https://socialify.git.ci/Ignorance-of-Dong/optimization_open_neteasy_cloud/image?language=1&owner=1&stargazers=1&theme=Light) 2 | # 网易云音乐 3 | 🎅❄️🎶Optimization project 4 | 5 | ### react网易云音乐(H5) 6 | 7 | - [api来源](https://github.com/Binaryify/NeteaseCloudMusicApi)(感谢Binaryify不断更新的网易云音乐接口,这也将是这个项目不断拓展下去的坚实依托) 8 | 9 | - [源码地址](https://github.com/Ignorance-of-Dong/optimization_open_neteasy_cloud) 10 | 11 | - [项目预览](http://music.fishfairy.cn/)(请在chrome调试模式下使用) 12 | 13 | ### 技术栈 14 | 15 | - react:整体使用react-hooks 16 | - Mobx:管理全局状态 17 | - react-router:管理单页面应用路由 18 | - fetch:发起http请求 19 | - Webpack:自动化构建工具,手动搭建webpack。 20 | - ES6:采用ES6语法。 21 | - CSS3:CSS3动画及样式。 22 | - typeScript: 使用ts规范变量声明 23 | - Ant Design Mobile: 阿里云移动UI库 24 | 25 | ### 功能介绍 26 | 27 | - 登陆 28 | - 首页 29 | - 每日推荐 30 | - 歌单广场 31 | - 排行榜 32 | - 云村热评 33 | - 视频 34 | - MV详情页 35 | - 我的 36 | - 电台模块【电台首页,电台详情, 电台排行榜】 37 | - 搜索【支持单曲,MV,专辑,歌单,电台】 38 | - 播放页【歌词,播放列表,上一首,下一首】 39 | 40 | ### 部分功能效果图 41 | 42 | ![](https://raw.githubusercontent.com/Ignorance-of-Dong/GraphBed/master/images/m1.png) 43 | ![](https://raw.githubusercontent.com/Ignorance-of-Dong/GraphBed/master/images/m2.png) 44 | ![](https://raw.githubusercontent.com/Ignorance-of-Dong/GraphBed/master/images/m3.png) 45 | ![](https://raw.githubusercontent.com/Ignorance-of-Dong/GraphBed/master/images/m4.png) 46 | ![](https://raw.githubusercontent.com/Ignorance-of-Dong/GraphBed/master/images/m5.png) 47 | ![](https://raw.githubusercontent.com/Ignorance-of-Dong/GraphBed/master/images/m6.png) 48 | 49 | 50 | ### 安装运行(安装运行前请确定已安装node环境) 51 | 52 | - 环境安装:npm install or yarn 53 | - 启动服务:npm run dev 54 | - 发布代码:npm run build 55 | 56 | 57 | ### **持续更新中...** 58 | -------------------------------------------------------------------------------- /src/views/PgLogin/PgPasswordInput/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .password-wrap{ 3 | width: 100%; 4 | height: px2rem(300); 5 | margin-top:px2rem(100); 6 | // background: #ccc; 7 | .password-tips{ 8 | width: 100%; 9 | font-size: px2rem(35); 10 | padding:px2rem(60) px2rem(30); 11 | box-sizing: border-box; 12 | color: #ccc; 13 | } 14 | .password-input{ 15 | width: 100%; 16 | height: px2rem(80); 17 | padding:0 px2rem(30); 18 | box-sizing: border-box; 19 | .password-border{ 20 | width: 100%; 21 | height: 100%; 22 | border-bottom:px2rem(1) solid #ccc; 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | } 27 | } 28 | .password-forget{ 29 | // padding-right: px2rem(40); 30 | font-size: px2rem(15); 31 | color: cornflowerblue; 32 | } 33 | .password-text-wrap { 34 | flex:1; 35 | padding:0 px2rem(30); 36 | .password-text{ 37 | width: 100%; 38 | height: 100%; 39 | background: transparent; 40 | border:0; 41 | } 42 | } 43 | .password-buttom{ 44 | width: 90%; 45 | height: px2rem(95); 46 | background: #f33838; 47 | margin: 0 auto; 48 | margin-top: px2rem(60); 49 | border-radius: px2rem(50); 50 | text-align: center; 51 | line-height: px2rem(95); 52 | font-size: px2rem(35); 53 | color: #fff; 54 | 55 | &:active { 56 | background: #e07e7e; 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/views/PgLogin/PgPhoneInput/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Headers, Toasts } from 'components/index' 3 | import './index.scss' 4 | function PgPhoneInput(props: any) { 5 | let [phone, setphone] = useState('') 6 | 7 | function toPassword(): void { 8 | if (phone.length < 11) { 9 | Toasts('请输入11位数字的手机号', 2000) 10 | return 11 | } 12 | props.history.push(`/loginpassword?phone=${phone}`) 13 | } 14 | return ( 15 | <> 16 | 17 |
18 |

19 | 未注册的手机号登陆后自动创建账户 20 |

21 |
22 |
23 |
24 | +86 25 |
26 |
27 | { 28 | setphone(e.target.value) 29 | }} /> 30 |
31 |
32 | x 33 |
34 |
35 |
36 |
{ 37 | toPassword() 38 | 39 | }}> 40 | 下一步 41 |
42 |
43 | 44 | 45 | ) 46 | } 47 | 48 | export default PgPhoneInput -------------------------------------------------------------------------------- /src/components/Toast/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .over-toast { 4 | width: auto; 5 | position: fixed; 6 | left: 50%; 7 | bottom: 20%; 8 | border-radius: px2rem(10); 9 | transform: translateX(-50%); 10 | background: #000; 11 | padding: px2rem(20) px2rem(30); 12 | white-space: nowrap; 13 | box-shadow: 1px 1px 1px #ccc; 14 | color: #a08383; 15 | z-index: 999999999999999; 16 | } 17 | 18 | .loding-wrap { 19 | width: 100%; 20 | height: 100%; 21 | position: fixed; 22 | top: 0; 23 | left: 0; 24 | z-index: 900; 25 | 26 | .loding-mask { 27 | width: 100%; 28 | height: 100%; 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | z-index: 1000; 33 | } 34 | 35 | .loading-contant { 36 | width: px2rem(200); 37 | height: px2rem(200); 38 | position: fixed; 39 | top: 50%; 40 | z-index: 2000; 41 | left: 50%; 42 | opacity: .5; 43 | transform: translateX(-50%) translateY(-50%); 44 | 45 | .loding-pic { 46 | width: px2rem(200); 47 | height: px2rem(200); 48 | border-radius: 50%; 49 | overflow: hidden; 50 | margin: px2rem(20) auto; 51 | animation: roate 1.4s ease infinite; 52 | } 53 | @keyframes roate { 54 | 0%{ 55 | transform: rotateZ(180deg); 56 | } 57 | 50% { 58 | transform: rotateZ(360deg); 59 | } 60 | 100% { 61 | transform: rotateZ(-180deg); 62 | } 63 | } 64 | 65 | img { 66 | width: 100%; 67 | height: 100%; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/components/Skeleton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.scss' 3 | interface props{ 4 | type: number 5 | } 6 | function Skeleton(props: props) { 7 | 8 | const BannerSkeleton = () => { 9 | return <> 10 | 15 | 20 | 21 | 22 | } 23 | 24 | const SongSheetSkeleton = () => { 25 | return <> 26 | { 27 | [1, 2, 3].map(item => { 28 | return ( 29 |
30 |
31 |
32 |
33 | ) 34 | }) 35 | } 36 | 37 | } 38 | 39 | const VideoSkeleton = () => { 40 | return <> 41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | } 49 | 50 | const TemplateList: Array = [, , ] 51 | return <> 52 | {TemplateList[props.type]} 53 | 54 | } 55 | 56 | export default Skeleton -------------------------------------------------------------------------------- /tsconfig.md: -------------------------------------------------------------------------------- 1 | ## 默认tsconfig 2 | 3 | { 4 | "compilerOptions": { 5 | //在类的外部使用this关键字时,它会默认获得any类型 6 | //不允许 没有明确指定类型(或通过类型推断)的 this被使用 7 | "noImplicitThis": true, 8 | "removeComments": true, 9 | "preserveConstEnums": true, 10 | "sourceMap": true, 11 | //以上为默认 12 | "outDir": "./built", 13 | "allowJs": true, 14 | "target": "es5", 15 | //防止你忘记在函数末尾返回值 16 | "noImplicitReturns": true, 17 | //防止在switch代码块里的两个case之间忘记添加break语句 18 | "noFallthroughCasesInSwitch": true, 19 | //发现那些执行不到的代码 20 | "allowUnreachableCode": true, 21 | //发现那些执行不到的标签 22 | "allowUnusedLabels": true, 23 | //不想在发生错误的时候,TypeScript还会被编译成JavaScript 24 | "noEmitOnError": true, 25 | //不想让TypeScript将没有明确指定的类型默默地推断为 any类型 26 | //"noImplicitAny": true, 27 | //启用一些模块系统 28 | "module": "umd", 29 | //严格的null与undefined检查 依赖也需要相应地启用strictNullChecks 30 | "strictNullChecks": true 31 | //默认所有可见的"@types"包会在编译过程中被包含进来 32 | //只有typeRoots下面的包才会被包含进来 33 | //"typeRoots" : ["./typings"], 34 | //如果指定了types,只有被列出来的包才会被包含进来 35 | //指定"types": []来禁用自动引入@types包 36 | //"types" : ["node", "lodash", "express"] 37 | }, 38 | //从另一个配置文件里继承配置 39 | //"extends": "./configs/base", 40 | //指定一个包含相对或绝对文件路径的列表 41 | //可以让IDE在保存文件的时候根据tsconfig.json重新生成文件 42 | //"compileOnSave": true, 43 | //一个对象的数组,指明要引用的工程 44 | // "references": [ 45 | // { "path": "../src" } 46 | // ], 47 | // "files": [], 48 | //指定一个文件glob匹配模式列表 49 | "include": [ 50 | // * 匹配0或多个字符(不包括目录分隔符) 51 | // ? 匹配一个任意字符(不包括目录分隔符) 52 | // **/ 递归匹配任意子目录 53 | "./src/**/*" 54 | ], 55 | "exclude": [ 56 | "node_modules", 57 | "**/*.spec.ts" 58 | ] 59 | } -------------------------------------------------------------------------------- /src/views/PgFirends/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-07-30 17:18:31 6 | * @Descripttion: 7 | */ 8 | import React, { useEffect } from 'react' 9 | import { Icons, Bubbleflow, Toasts } from 'components/index' 10 | import './index.scss' 11 | function PgFirends(props: any): JSX.Element { 12 | 13 | useEffect((): void => { 14 | console.log('进入云村入口页面') 15 | }, []) 16 | 17 | return ( 18 | <> 19 |
20 |
21 |
{ 22 | // props.history.push('/yuncun') 23 | Toasts("含泪关闭入口😭😭😭😭", 2000) 24 | }}> 25 |
26 |
27 | 云村热评墙 28 |
29 |
30 | 村友,这些评论戳中你的心了吗? 31 |
32 |
33 |
34 |
35 | {new Date().toDateString().split(" ")[1]}. 36 |
37 |
38 | {new Date().getDate()} 39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 | 47 |
48 | 49 | ) 50 | } 51 | 52 | export default PgFirends -------------------------------------------------------------------------------- /config/webpack.config.dev.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-12-04 10:10:00 4 | * @LastEditors: Mr.zheng 5 | * @LastEditTime: 2019-12-04 10:10:00 6 | * @Description: webpack本地开发文件 7 | */ 8 | const path = require('path') 9 | const merge = require('webpack-merge') 10 | const webpack = require('webpack') 11 | const webpackCommon = require('./webpack.config.common') 12 | const ProgressBarPlugin = require('progress-bar-webpack-plugin') // 显示进度条 13 | const ROOT_PATH = process.cwd(); 14 | const DIST_PATH = path.resolve(ROOT_PATH, "build"); 15 | import Config from '../web.config' 16 | const chalk = require('react-dev-utils/chalk') 17 | 18 | 19 | module.exports = merge(webpackCommon, { 20 | mode: 'development', 21 | output: { 22 | path: DIST_PATH, 23 | filename: "[name].[hash].js" 24 | }, 25 | devtool: "source-map", 26 | devServer: { // 本地为搭建了一个小型的静态文件服务器 27 | hot: true, 28 | host: "0.0.0.0", // 可以使用手机访问 29 | port: Config.Serverport, 30 | compress: true, // 一切服务都将启动gzip压缩 31 | clientLogLevel: "none", // 禁止浏览器控制台上输出热重载进度【这可能很繁琐】 32 | noInfo: true, // 启用以后【其他信息会被隐藏而错误和警告仍会显示】 33 | quiet: true, // 清除webpack【热重载默认在虚拟环境下打包,不在终端显示】 34 | // proxy: { // 代理到后端的服务地址,会拦截所有以api开头的请求地址 35 | // "/api": "http://localhost:4000" 36 | // } 37 | }, 38 | plugins: [ 39 | new ProgressBarPlugin({ 40 | format: ' Avtion [:bar] ' + ':percent' + ' (:elapsed seconds)', 41 | clear: false, 42 | callback: () => { 43 | console.log(' \n 成功启动服务!!!😊😊😊') 44 | console.log(` \n Local: ${chalk.green(`http://localhost:${Config.Serverport}/`)}`) 45 | console.log(` On Your Network: ${chalk.green(`http://${Config.IPv4}:${Config.Serverport}/`)}`) 46 | console.log('\n\nNote that the development build is not optimized.') 47 | console.log(`To create a production build, use ${chalk.yellow('npm run build')}.`) 48 | } 49 | }), 50 | new webpack.HotModuleReplacementPlugin(), // 对文件实现热更新 51 | ] 52 | }) -------------------------------------------------------------------------------- /src/views/PgFirends/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .yuncun-wraps{ 4 | width: 100%; 5 | height: 100%; 6 | overflow: hidden; 7 | position: relative; 8 | .load-bearing { 9 | position: absolute; 10 | top: px2rem(300); 11 | width: 100%; 12 | height: px2rem(900); 13 | // background: #ccc; 14 | } 15 | .yuncun-conation { 16 | height: 100%; 17 | overflow-y: auto; 18 | .yuncun-tip { 19 | width: 95%; 20 | height: px2rem(200); 21 | margin: px2rem(20) auto; 22 | background-image: url(https://p1.music.126.net/8pZU75UDx6hooyYp1hzc9A==/2535473813822413.jpg); 23 | // background-size: 100% 100%; 24 | background-position: center; 25 | filter: 120px; 26 | border-radius: px2rem(15); 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: center; 30 | .left { 31 | height: 100%; 32 | flex: 1; 33 | color: #fff; 34 | display: flex; 35 | flex-direction: column; 36 | padding: px2rem(50); 37 | .left-title { 38 | width: 100%; 39 | height: 50%; 40 | } 41 | .left-contant { 42 | width: 100%; 43 | height: 50%; 44 | padding-top: px2rem(15); 45 | font-size: px2rem(30); 46 | } 47 | } 48 | .right { 49 | width: px2rem(150); 50 | height: 100%; 51 | // background: grey; 52 | display: flex; 53 | flex-direction: column; 54 | padding: px2rem(50); 55 | .right-mouth { 56 | font-size: px2rem(35); 57 | font-weight: 800; 58 | color: #fff; 59 | } 60 | .right-day { 61 | font-size: px2rem(60); 62 | font-weight: 800; 63 | color: #fff; 64 | } 65 | } 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/views/PgHome/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .home-wrap { 4 | width: 100%; 5 | height: 100%; 6 | background: rgb(172, 6, 6); 7 | position: relative; 8 | 9 | .home-logo { 10 | position: absolute; 11 | left: 50%; 12 | top: 20%; 13 | border-radius: 50%; 14 | overflow: hidden; 15 | width: px2rem(48 * 3); 16 | height: px2rem(48 * 3); 17 | // margin-left: -px2rem(48 * 2); 18 | transform: translateX(-50%); 19 | 20 | .home-logo-wh { 21 | width: 100%; 22 | height: 100%; 23 | } 24 | } 25 | 26 | .home-button { 27 | width: 100%; 28 | height: px2rem(300); 29 | // background: #ccc; 30 | position: absolute; 31 | bottom: px2rem(50); 32 | 33 | .iphone-login { 34 | width: 75%; 35 | height: px2rem(90); 36 | background: #fff; 37 | margin: 0 auto; 38 | margin-top: px2rem(15); 39 | border-radius: px2rem(50); 40 | text-align: center; 41 | line-height: px2rem(90); 42 | color: #C20C0C; 43 | 44 | &:active { 45 | background: #e07e7e; 46 | } 47 | } 48 | 49 | .temporary-login { 50 | width: 75%; 51 | height: px2rem(90); 52 | background: #ccc; 53 | margin: 0 auto; 54 | margin-top: px2rem(25); 55 | border-radius: px2rem(40); 56 | text-align: center; 57 | line-height: px2rem(90); 58 | box-sizing: border-box; 59 | // padding:px2rem(1) px2rem(1); 60 | padding: 1px; 61 | 62 | // overflow: hidden; 63 | // padding-left:px2rem(1); 64 | &:active { 65 | background: #e07e7e; 66 | } 67 | 68 | .count-but { 69 | // width: 100%; 70 | height: 100%; 71 | background: #C20C0C; 72 | border-radius: px2rem(40); 73 | margin: 0 px2rem(1); 74 | color: #fff; 75 | 76 | &:active { 77 | background: #e07e7e; 78 | } 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /config/webpack.config.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-12-04 10:10:00 4 | * @LastEditors: Mr.zheng 5 | * @LastEditTime: 2019-12-04 10:10:00 6 | * @Description: webpack测试文件 7 | */ 8 | import path from 'path' 9 | import merge from 'webpack-merge' 10 | import webpack from 'webpack' 11 | const webpackCommon = require('./webpack.config.common') 12 | const ProgressBarPlugin = require('progress-bar-webpack-plugin') // 显示进度条 13 | const ROOT_PATH = process.cwd(); 14 | const DIST_PATH = path.resolve(ROOT_PATH, "build"); 15 | import Config from '../web.config' 16 | const chalk = require('react-dev-utils/chalk') 17 | 18 | 19 | module.exports = merge(webpackCommon, { 20 | mode: 'development', 21 | entry: { 22 | build: path.resolve(process.cwd(), 'test/index.tsx') 23 | }, 24 | output: { 25 | path: DIST_PATH, 26 | filename: "[name].[hash].js" 27 | }, 28 | devtool: "source-map", 29 | devServer: { 30 | hot: true, 31 | host: "0.0.0.0", // 可以使用手机访问 32 | port: Config.Serverport, 33 | compress: true, // 一切服务都将启动gzip压缩 34 | clientLogLevel: "none", // 禁止浏览器控制台上输出热重载进度【这可能很繁琐】 35 | noInfo: true, // 启用以后【其他信息会被隐藏而错误和警告仍会显示】 36 | quiet: true, // 清除webpack【热重载默认在虚拟环境下打包,不在终端显示】 37 | // proxy: { // 代理到后端的服务地址,会拦截所有以api开头的请求地址 38 | // "/api": "http://localhost:4000" 39 | // } 40 | }, 41 | plugins: [ 42 | new ProgressBarPlugin({ 43 | format: ' Avtion [:bar] ' + ':percent' + ' (:elapsed seconds)', 44 | clear: false, 45 | callback: () => { 46 | console.log(' \n 成功启动服务!!!😊😊😊') 47 | console.log(` \n Local: http://localhost:${Config.Serverport}/`) 48 | console.log(` On Your Network: http://${Config.IPv4}:${Config.Serverport}/`) 49 | console.log('\n\nNote that the development build is not optimized.') 50 | console.log(`To create a production build, use ${chalk.yellow('npm runs build')}.`) 51 | } 52 | }), 53 | new webpack.HotModuleReplacementPlugin(), // 对文件实现热更新 54 | ] 55 | }) -------------------------------------------------------------------------------- /src/views/PgRankingList/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-07-30 15:59:46 6 | * @Descripttion: 7 | */ 8 | import React from 'react'; 9 | import './index.scss' 10 | import { Headers } from 'components/index' 11 | import Singlerowdata from './singleRowData' 12 | 13 | function PgRankingList(props: any): JSX.Element { 14 | 15 | return <> 16 |
17 | 排行榜 18 |
19 | { 20 | Singlerowdata.map((item, index) => { 21 | return ( 22 |
23 |
24 | {item.title} 25 |
26 |
27 | { 28 | item.datalist.map(items => { 29 | return ( 30 |
{ 31 | props.history.push(`/playdetails?id=${items.id}&isList={true}`) 32 | }}> 33 |
34 | 35 |
36 |
37 | {items.name} 38 |
39 |
40 | ) 41 | }) 42 | } 43 |
44 |
45 | ) 46 | }) 47 | } 48 | 49 |
50 |
51 | 52 | } 53 | export default PgRankingList -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optimization_open_neteasy_cloud", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "webpack-dev-server --config ./config/webpack.config.test.ts --color", 8 | "dev": "webpack-dev-server --config ./config/webpack.config.dev.ts --color", 9 | "build": "webpack --config ./config/webpack.config.prod.ts --color" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "antd-mobile": "^2.3.1", 15 | "lyric-parser": "^1.0.1", 16 | "mobx": "^5.15.0", 17 | "mobx-react": "^6.1.4", 18 | "qqmap": "^1.0.1", 19 | "react": "^16.12.0", 20 | "react-qmap": "^0.1.6", 21 | "swiper": "^5.2.1", 22 | "video-react": "^0.14.1" 23 | 24 | }, 25 | "browsers": [ 26 | "last 30 versions", 27 | "> 2%", 28 | "Firefox >= 10", 29 | "ie 6-11" 30 | ], 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | }, 43 | "devDependencies": { 44 | "mini-css-extract-plugin": "^0.8.0", 45 | "autoprefixer": "^9.7.3", 46 | "node-sass": "^4.13.0", 47 | "babel-loader": "^8.0.6", 48 | "babel-plugin-import": "^1.13.0", 49 | "clean-webpack-plugin": "^3.0.0", 50 | "copy-webpack-plugin": "^5.0.5", 51 | "css-loader": "^3.2.1", 52 | "file-loader": "^5.0.2", 53 | "html-webpack-plugin": "^3.2.0", 54 | "react-router-dom": "^5.1.2", 55 | "sass-loader": "^8.0.0", 56 | "style-loader": "^1.0.1", 57 | "optimize-css-assets-webpack-plugin": "^5.0.3", 58 | "postcss-loader": "^3.0.0", 59 | "progress-bar-webpack-plugin": "^1.12.1", 60 | "react-dev-utils": "^9.1.0", 61 | "react-dom": "^16.12.0", 62 | "webpack": "^4.41.2", 63 | "webpack-cli": "^3.3.10", 64 | "webpack-dev-server": "^3.9.0", 65 | "webpack-merge": "^4.2.2", 66 | "@babel/core": "^7.7.4", 67 | "uglifyjs-webpack-plugin": "^2.2.0", 68 | "url-loader": "^3.0.0", 69 | "typescript": "^3.7.2", 70 | "terser-webpack-plugin": "^2.3.1", 71 | "ts-node": "^8.5.4", 72 | "@babel/plugin-proposal-class-properties": "^7.7.4", 73 | "@babel/plugin-proposal-decorators": "^7.7.4", 74 | "@babel/plugin-transform-classes": "^7.7.4", 75 | "@babel/plugin-transform-runtime": "^7.7.4", 76 | "@babel/preset-env": "^7.7.4", 77 | "@babel/preset-react": "^7.7.4", 78 | "@babel/preset-typescript": "^7.7.4", 79 | "@types/node": "^12.12.14", 80 | "@types/react": "^16.9.14", 81 | "@types/webpack": "^4.41.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/GloblePlayer/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2021-08-03 14:53:15 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-16 16:48:28 6 | * @Descripttion: 7 | */ 8 | 9 | import React, { useCallback, useEffect } from "react" 10 | import "./index.scss" 11 | import { inject, observer } from 'mobx-react' 12 | import { Headers, Icons, Toasts } from "components/index"; 13 | 14 | function GloblePlayer(props) { 15 | let { handlePlayerState, handleSongTotalTime, handlePlayOrPause, handleSongCurrentTime, handleGetPic, handleGetName } = props.playerStore; 16 | useEffect((): void => { 17 | console.log(props.playerStore.audiosRef) 18 | if (props.playerStore.playerState) { 19 | props.playerStore.audiosRef.current 20 | .play() 21 | .then(res => { 22 | handleSongTotalTime(props.playerStore.audiosRef.current.duration); 23 | props.playerStore.audiosRef.current.addEventListener( 24 | "timeupdate", 25 | handleSongCurrentTime, 26 | false 27 | ); 28 | props.playerStore.audiosRef.current.addEventListener("ended", handlePlayOrPause, false); 29 | }) 30 | .catch(err => { 31 | props.playerStore.audiosRef.current.pause(); 32 | handlePlayerState(false); 33 | Toasts("加载歌曲失败,请重试!", 2000); 34 | }); 35 | } else { 36 | props.playerStore.audiosRef && props.playerStore.audiosRef.current.pause(); 37 | } 38 | }, [props.playerStore.playerState, props.playerStore.audiosRef]); 39 | return <> 40 |
41 |
{window.location.href = '/#/musicplayer'}}> 42 | 43 |
44 |
{ handleGetName(props.playerStore.songDetail)}
45 | { 48 | handlePlayerState(!props.playerStore.playerState); 49 | }} 50 | > 51 | {props.playerStore.playerState ? ( 52 | 53 | ) : ( 54 | 55 | )} 56 | 57 | {/*
*/} 58 |
59 | 60 | } 61 | 62 | export default inject('playerStore')(observer(GloblePlayer)) -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-17 15:05:16 6 | * @Descripttion: 7 | */ 8 | import routerConfig from './router' 9 | import React, { memo, useEffect, useRef, useState } from 'react' 10 | import RouterView from './router/routerView' 11 | import './App.css' 12 | import GloblePlayer from "components/GloblePlayer" 13 | import { inject, observer } from "mobx-react"; 14 | 15 | 16 | const RouterViewPro = memo(RouterView) 17 | function App(props: any) { 18 | console.log(props) 19 | let { handleGetAudioRef } = props.playerStore; 20 | let audiosRef = useRef(null); 21 | let bool:any={}; 22 | console.log(sessionStorage.getItem("meta")) 23 | if (sessionStorage.getItem("meta") == "undefined" || !sessionStorage.getItem("meta")) { 24 | bool = {isGloblePlayer: false} 25 | } else { 26 | bool = JSON.parse(sessionStorage.getItem("meta")); 27 | } 28 | // console.log(sessionStorage.getItem("meta") ? "{}" : 1) 29 | // let status = JSON.parse(sessionStorage.getItem("meta") || "{}"); 30 | let result = false 31 | if (bool.isGloblePlayer && sessionStorage.getItem("songListDetails")) { 32 | result = bool.isGloblePlayer 33 | } else { 34 | result = false 35 | } 36 | console.log(bool) 37 | let [globelStatus, setGlobelStatus] = useState(result) 38 | useEffect(() => { 39 | handleGetAudioRef(audiosRef) 40 | console.log(props.playerStore.audiosRef) 41 | 42 | window.addEventListener('hashchange', (router) => { 43 | console.log(router, "router") 44 | let bool:any = {}; 45 | if (sessionStorage.getItem("meta") == "undefined" || !sessionStorage.getItem("meta")) { 46 | bool = {isGloblePlayer: false} 47 | } else { 48 | bool = JSON.parse(sessionStorage.getItem("meta")); 49 | } 50 | console.log(bool) 51 | console.log(bool && sessionStorage.getItem("songListDetails")) 52 | if (bool && sessionStorage.getItem("songListDetails")) { 53 | setGlobelStatus(bool.isGloblePlayer) 54 | } else { 55 | setGlobelStatus(false) 56 | } 57 | 58 | }) 59 | }, [audiosRef]) 60 | console.log(globelStatus, "11") 61 | return <> 62 | 63 | 64 |
65 | 66 |
67 | 68 | 69 |
70 |
72 | 73 | } 74 | 75 | export default inject("playerStore")(observer(App)); 76 | -------------------------------------------------------------------------------- /src/utils/fetch.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2020-08-05 16:07:26 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-11-17 14:46:53 6 | * @Descripttion: 7 | */ 8 | import { Toasts, ToastLodingPro } from "components/index"; 9 | const BASEHOST = 'http://musicapi.fishfairy.cn' 10 | // const BASEHOST = "http://localhost:3000"; 11 | 12 | class Fetch { 13 | urlList = {}; 14 | get(url, params?) { 15 | if (params) { 16 | var paramsArray = []; 17 | Object.keys(params).forEach(function (key) { 18 | paramsArray.push(key + "=" + params[key]); 19 | }); 20 | if (url.search(/\?/) === -1) { 21 | url += "?" + paramsArray.join("&"); 22 | } else { 23 | url += "&" + paramsArray.join("&"); 24 | } 25 | } 26 | let fetchConfig: any = { 27 | method: "get", 28 | headers: { 29 | authorization: window.sessionStorage.getItem("token") 30 | ? window.sessionStorage.getItem("token") 31 | : null, 32 | "Content-Type": "application/json; charset=utf-8" 33 | }, 34 | credentials: "include", 35 | mode: "cors" 36 | }; 37 | return fetch(BASEHOST + url, fetchConfig).then(response => { 38 | return response.json().then(res => { 39 | if (response.ok && res.code === 200) { 40 | return Promise.resolve(res); 41 | } else { 42 | return Promise.reject(res); 43 | } 44 | }); 45 | }); 46 | } 47 | post(url, options?, signal?): Promise { 48 | if (Object.keys(this.urlList).length === 0) { 49 | ToastLodingPro.loading(); 50 | } 51 | this.urlList[url] = url; 52 | 53 | return fetch(BASEHOST + url, { 54 | ...signal, 55 | method: "post", 56 | headers: { 57 | // authorization: window.sessionStorage.getItem('token') ? window.sessionStorage.getItem('token') : null, 58 | // 'Content-Type': 'application/json; charset=utf-8' 59 | // token: sessionStorage.getItem('token') ? sessionStorage.getItem('token') : 'GSVDADGFN_WDBSADVD' 60 | }, 61 | credentials: "include", 62 | mode: "cors", 63 | body: JSON.stringify(options) 64 | }) 65 | .then(response => { 66 | return response.json().then(res => { 67 | delete this.urlList[url]; // 每次请求成功后 都删除队列里的路径 68 | if (Object.keys(this.urlList).length === 0) { 69 | ToastLodingPro.hide(); 70 | } 71 | if (response.ok && res.code === 200) { 72 | return Promise.resolve(res); 73 | } else { 74 | Toasts(res.msg || "网络请求异常", 2000); 75 | return Promise.reject(res); 76 | } 77 | }); 78 | }) 79 | .catch(err => { 80 | delete this.urlList[url]; // 每次请求成功后 都删除队列里的路径 81 | if (Object.keys(this.urlList).length === 0) { 82 | ToastLodingPro.hide(); 83 | } 84 | Toasts("网络请求异常,请两分钟后再试", 2000); 85 | }); 86 | } 87 | } 88 | 89 | export default new Fetch(); 90 | -------------------------------------------------------------------------------- /src/views/PgRadioList/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | 4 | .radiolist-wrap { 5 | width: 100%; 6 | height: 100%; 7 | display: flex; 8 | flex-direction: column; 9 | .radiolist-tab { 10 | width: 100%; 11 | height: px2rem(100); 12 | // background: red; 13 | display: flex; 14 | justify-content: space-around; 15 | align-items: center; 16 | box-shadow: 1px 1px 2px #ccc; 17 | .radiolist-tab-new { 18 | height: 100%; 19 | line-height: px2rem(100); 20 | position: relative; 21 | font-size: px2rem(30); 22 | } 23 | .radiolist-tab-hot { 24 | height: 100%; 25 | line-height: px2rem(100); 26 | position: relative; 27 | font-size: px2rem(30); 28 | } 29 | .line { 30 | position: absolute; 31 | bottom: 0; 32 | width: 100%; 33 | height: px2rem(6); 34 | background: red; 35 | } 36 | } 37 | .radiolist-content { 38 | width: 100%; 39 | flex: 1; 40 | // background: #ccc; 41 | overflow-y: auto; 42 | -webkit-overflow-scrolling: touch; 43 | .radiolist-tip { 44 | width: 100%; 45 | height: px2rem(230); 46 | // background: green; 47 | display: flex; 48 | justify-content: flex-start; 49 | align-items: center; 50 | .r-t-serial { 51 | width: px2rem(100); 52 | height: 100%; 53 | // background: #ccc; 54 | line-height: px2rem(230); 55 | text-align: center; 56 | font-size: px2rem(60); 57 | color: gray; 58 | font-weight: 400; 59 | } 60 | .r-t-pic { 61 | width: px2rem(180); 62 | height: px2rem(180); 63 | border-radius: px2rem(20); 64 | // background: red; 65 | overflow: hidden; 66 | img{ 67 | width: 100%; 68 | height: 100%; 69 | } 70 | } 71 | .r-t-content { 72 | flex: 1; 73 | height: px2rem(200); 74 | // background: yellow; 75 | display: flex; 76 | flex-direction: column; 77 | justify-content: center; 78 | padding-left: px2rem(15); 79 | .radiolist-name { 80 | font-size: px2rem(35); 81 | padding-bottom: px2rem(10); 82 | } 83 | .radiolist-author { 84 | font-size: px2rem(25); 85 | padding-bottom: px2rem(10); 86 | color: gray; 87 | } 88 | .radiolist-hot { 89 | color: gray; 90 | font-size: px2rem(25); 91 | } 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /config/webpack.config.common.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-12-04 10:10:00 4 | * @LastEditors: Mr.zheng 5 | * @LastEditTime: 2019-12-04 10:10:00 6 | * @Description: webpack公共配置【对文件的处理以及解析】 7 | */ 8 | import path from 'path' 9 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | import Config from '../web.config' 11 | 12 | let { ComponentLibrary, ResolveAlias } = Config 13 | module.exports = { 14 | entry: { 15 | build: path.resolve(process.cwd(), 'src/index.tsx') 16 | }, 17 | output: { 18 | // 输出目录 19 | path: path.resolve(__dirname, '../music') 20 | }, 21 | resolve: { 22 | extensions: ['.tsx', '.ts', '.js', '.jsx', '.css', '.scss' ], 23 | alias: { 24 | ...ResolveAlias 25 | } 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.(sa|sc|c)ss$/, 31 | // 使用 'style-loader','css-loader' 32 | use: [ 33 | 'style-loader', 34 | 'css-loader', 35 | { 36 | loader: 'postcss-loader', 37 | options: { 38 | ident: 'postcss', 39 | plugins: [ 40 | require('autoprefixer'), 41 | ] 42 | } 43 | }, 44 | 'sass-loader', 45 | ] 46 | }, 47 | { 48 | test: /\.(le)ss$/, 49 | // 使用 'style-loader','css-loader' 50 | use: [ 51 | 'style-loader', 52 | 'css-loader', 53 | { 54 | loader: 'postcss-loader', 55 | options: { 56 | ident: 'postcss', 57 | plugins: [ 58 | require('autoprefixer'), 59 | ] 60 | } 61 | }, 62 | 'less-loader' 63 | ] 64 | }, 65 | { 66 | test: /\.(js|jsx|ts|tsx)$/, 67 | use: { 68 | loader: 'babel-loader', 69 | options: { 70 | presets: [ 71 | '@babel/preset-react', 72 | '@babel/preset-env', 73 | '@babel/preset-typescript', 74 | ], 75 | plugins: [ 76 | [ 77 | '@babel/plugin-proposal-decorators',{'legacy': true} 78 | ], 79 | [ 80 | '@babel/plugin-proposal-class-properties', 81 | ], 82 | ['@babel/plugin-transform-classes',{'loose': true}], 83 | ['@babel/plugin-transform-runtime'], 84 | [ 85 | 'import', { 86 | 'libraryName': ComponentLibrary, 87 | 'libraryDirectory': 'es', 88 | 'style': 'css', 89 | } 90 | ] 91 | ] 92 | } 93 | }, 94 | exclude: path.resolve(process.cwd(), 'node_modules') 95 | }, 96 | { 97 | test: /\.(png|jpg|jpeg|gif|svg)/, 98 | use: { 99 | loader: 'url-loader', 100 | options: { 101 | outputPath: 'images/', // 图片输出的路径 102 | limit: 10 * 1024 103 | } 104 | } 105 | }, 106 | { 107 | test: /\.(eot|woff2?|ttf|svg)$/, 108 | use: [ 109 | { 110 | loader: 'url-loader', 111 | options: { 112 | name: '[name]-[hash:5].min.[ext]', 113 | limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file 114 | publicPath: 'fonts/', 115 | outputPath: 'fonts/' 116 | } 117 | } 118 | ] 119 | } 120 | ] 121 | }, 122 | plugins: [ 123 | new HtmlWebpackPlugin({ // 生成html文件 124 | filename: 'index.html', // 最终创建的文件名 125 | minify: { 126 | caseSensitive:false, 127 | collapseWhitespace:true, 128 | removeAttributeQuotes:true, 129 | removeComments:true 130 | }, 131 | template: path.resolve(process.cwd(), 'public/index.html'), // 指定模板路径 132 | }) 133 | ], 134 | } 135 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @import './assets/style/px2rem.scss'; 2 | @font-face { 3 | font-family: 'iconfont'; 4 | /* project id 1355540 */ 5 | src: url('//at.alicdn.com/t/font_1355540_1l1phwrr3fm.eot'); 6 | src: url('//at.alicdn.com/t/font_1355540_1l1phwrr3fm.eot?#iefix') format('embedded-opentype'), 7 | url('//at.alicdn.com/t/font_1355540_1l1phwrr3fm.woff2') format('woff2'), 8 | url('//at.alicdn.com/t/font_1355540_1l1phwrr3fm.woff') format('woff'), 9 | url('//at.alicdn.com/t/font_1355540_1l1phwrr3fm.ttf') format('truetype'), 10 | url('//at.alicdn.com/t/font_1355540_1l1phwrr3fm.svg#iconfont') format('svg'); 11 | } 12 | .iconfont { 13 | font-family: 'iconfont'!important; 14 | font-size: px2rem(28); 15 | font-style:normal; 16 | } 17 | 18 | * { 19 | -webkit-appearance: none; 20 | box-sizing: border-box; 21 | /* -webkit-transform: translate3d(0, 0, 0); 22 | 23 | -moz-transform: translate3d(0, 0, 0); 24 | 25 | -ms-transform: translate3d(0, 0, 0); 26 | 27 | transform: translate3d(0, 0, 0); */ 28 | } 29 | .small { 30 | transform: scale(0.8); 31 | } 32 | .bigsmall { 33 | transform: scale(0.7); 34 | } 35 | blockquote, 36 | body, 37 | button, 38 | code, 39 | dd, 40 | div, 41 | dl, 42 | dt, 43 | fieldset, 44 | form, 45 | h1, 46 | h2, 47 | h3, 48 | h4, 49 | h5, 50 | h6, 51 | input, 52 | legend, 53 | li, 54 | ol, 55 | p, 56 | pre, 57 | td, 58 | textarea, 59 | th, 60 | ul, 61 | section { 62 | margin: 0; 63 | padding: 0; 64 | } 65 | 66 | table { 67 | border-collapse: collapse; 68 | border-spacing: 0; 69 | } 70 | 71 | fieldset, 72 | img, 73 | button { 74 | border: none; 75 | } 76 | 77 | address, 78 | caption, 79 | cite, 80 | code, 81 | dfn, 82 | em, 83 | strong, 84 | th, 85 | var { 86 | font-style: normal; 87 | font-weight: 400; 88 | } 89 | 90 | ol, 91 | ul { 92 | list-style: none; 93 | } 94 | 95 | li { 96 | list-style: none; 97 | } 98 | 99 | q:after, 100 | q:before { 101 | content: ''; 102 | } 103 | 104 | button, 105 | input, 106 | select, 107 | textarea { 108 | font: 100% "Microsoft YaHei", tahoma, arial, 'Hiragino Sans GB', '宋体', sans-serif; 109 | } 110 | 111 | a { 112 | text-decoration: none; 113 | } 114 | 115 | input::-ms-clear { 116 | display: none; 117 | } 118 | 119 | input::-ms-reveal { 120 | display: none; 121 | } 122 | 123 | input:focus { 124 | outline: none; 125 | } 126 | 127 | body, 128 | html { 129 | height: 100%; 130 | overflow: hidden; 131 | } 132 | 133 | 134 | img, 135 | span, 136 | input { 137 | display: inline-block; 138 | *display: inline; 139 | *zoom: 1; 140 | } 141 | 142 | .sp-span span { 143 | display: inline; 144 | } 145 | 146 | button { 147 | background-color: transparent; 148 | outline: none; 149 | } 150 | 151 | #root { 152 | width: 100%; 153 | height: 100%; 154 | background: #fff; 155 | } 156 | 157 | @media screen and (min-width:430px) { 158 | #root { 159 | width: 430px; 160 | height: 100%; 161 | overflow: hidden; 162 | margin: 0 auto !important; 163 | position: relative; 164 | } 165 | } 166 | 167 | @media screen and (max-width:430px) { 168 | #root { 169 | width: 100%; 170 | position: relative; 171 | height: 100%; 172 | background: #fff; 173 | } 174 | } 175 | .audios { 176 | background: #000; 177 | position: absolute; 178 | z-index: -888; 179 | opacity: 0; 180 | } 181 | .none { 182 | display: none; 183 | } 184 | /* .root-container { 185 | padding-bottom: px2rem(100); 186 | } */ -------------------------------------------------------------------------------- /src/views/PgRankingList/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .ranking-list-wrap { 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | .rangking-list-contant { 9 | width: 100%; 10 | flex: 1; 11 | overflow-y: auto; 12 | // background: #ccc; 13 | .rangking-list-tip { 14 | width: 100%; 15 | .rangking-list-tip-header { 16 | width: 100%; 17 | height: px2rem(100); 18 | background: #fff; 19 | padding-left: px2rem(30); 20 | font-weight: 800; 21 | line-height: px2rem(100); 22 | font-size: px2rem(35); 23 | } 24 | .rangking-list-tip-list { 25 | width: 100%; 26 | display: flex; 27 | justify-content: space-between; 28 | align-items: center; 29 | flex-wrap: wrap; 30 | padding: px2rem(20); 31 | @media screen and (min-width:400px) { 32 | .rangking-list-tip-low { 33 | width: px2rem(280); 34 | height: px2rem(350); 35 | // background: red; 36 | margin-bottom: px2rem(20); 37 | 38 | .rangking-list-tip-pic { 39 | width: 100%; 40 | height: px2rem(280); 41 | // background: yellow; 42 | border-radius: px2rem(15); 43 | overflow: hidden; 44 | img { 45 | width: 100%; 46 | height: 100%; 47 | } 48 | } 49 | .rangking-list-tip-name { 50 | width: 100%; 51 | height: px2rem(70); 52 | font-size: px2rem(25); 53 | line-height: px2rem(70); 54 | overflow: hidden; 55 | text-overflow: ellipsis; 56 | white-space: nowrap; 57 | } 58 | } 59 | } 60 | @media screen and (max-width:400px) { 61 | .rangking-list-tip-low { 62 | width: px2rem(250); 63 | height: px2rem(320); 64 | margin-bottom: px2rem(20); 65 | 66 | .rangking-list-tip-pic { 67 | width: 100%; 68 | height: px2rem(250); 69 | border-radius: px2rem(15); 70 | overflow: hidden; 71 | img { 72 | width: 100%; 73 | height: 100%; 74 | } 75 | } 76 | .rangking-list-tip-name { 77 | width: 100%; 78 | height: px2rem(70); 79 | // background: yellow; 80 | font-size: px2rem(25); 81 | line-height: px2rem(70); 82 | overflow: hidden; 83 | text-overflow: ellipsis; 84 | white-space: nowrap; 85 | } 86 | } 87 | } 88 | 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/views/PgAbout/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useCallback, useState } from 'react'; 2 | import { Headers } from 'components/index' 3 | // import QQMap from 'qqmap'; 4 | import ReactQMap from 'react-qmap'; 5 | import './index.scss' 6 | let classMap, windowMap 7 | let start, end 8 | function About (props){ 9 | const initialOptions = { 10 | zoomControl: false, 11 | mapTypeControl: false 12 | } 13 | const [center, setCenter] = useState({}) 14 | 15 | let audiosRef = useRef(null) 16 | useEffect(() => { 17 | // window.location.hash = "#reply"; 18 | if ("geolocation" in navigator) { 19 | /* geolocation is available */ 20 | navigator.geolocation.getCurrentPosition(_showPosition, _showError); 21 | } else { 22 | /* geolocation IS NOT available */ 23 | alert('你的浏览器不支持geolocation') 24 | } 25 | }, []) 26 | const _showPosition = useCallback((position) => { 27 | // setCenter({ latitude: position.coords.latitude, longitude: position.coords.longitude }) 28 | start = position.coords.latitude 29 | end = position.coords.longitude 30 | setCenter({ latitude: 39.9773000000, longitude: 116.4813200000 }) 31 | _setMarker(position.coords.latitude, position.coords.longitude); 32 | }, []) 33 | const _showError = useCallback((error) => { 34 | console.log(error); 35 | alert('获取定位异常'); 36 | }, []) 37 | const _setMarker = useCallback((latitude, longitude) => { 38 | const marker = new windowMap.Marker({ 39 | map: classMap, 40 | // position: new windowMap.LatLng(latitude, longitude), 41 | position: new windowMap.LatLng(39.9773000000, 116.4813200000), 42 | animation: windowMap.MarkerAnimation.DROP, 43 | }); 44 | }, []) 45 | 46 | 47 | const _getMap = useCallback((map, wMap) => { 48 | classMap = map; 49 | windowMap = wMap; 50 | const drivingService = new wMap.DrivingService({ 51 | map: map, 52 | }) 53 | // drivingService.setComplete(function (result) { 54 | // if (result.type == new windowMap.ServiceResultType.MULTI_DESTINATION) { 55 | // var d = result.detail; 56 | // drivingService.search(d.start[0], d.end[0]); 57 | // } 58 | 59 | // }); 60 | // drivingService.setPolicy(new windowMap.DrivingPolicy['LEAST_TIME']); 61 | // drivingService.setLocation("北京"); 62 | // drivingService.search('天坛公园', '将台'); 63 | 64 | // drivingService.setComplete(function (result) { 65 | // if (result.type == qq.maps.ServiceResultType.MULTI_DESTINATION) { 66 | // alert("起终点不唯一"); 67 | // } 68 | // alert(JSON.stringify(result)) 69 | // }); 70 | }, []) 71 | 72 | const goMap = () => { 73 | window.location.href = 'http://uri.amap.com/navigation?from=当前位置&to=39.9773000000,116.4813200000,北京诺金酒店二层&via=116.402796,39.936915,midwaypoint&mode=car&policy=1&src=mypage&coordinate=gaode&callnative=1' 74 | } 75 | return <> 76 |
77 | 头部 78 |
{ 79 | goMap() 80 | }}> 81 | _getMap(map, wMap)} 88 | /> 89 |
90 |
91 | 92 | } 93 | 94 | export default About -------------------------------------------------------------------------------- /config/webpack.config.prod.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-12-04 10:10:00 4 | * @LastEditors: Mr.zheng 5 | * @LastEditTime: 2019-12-04 10:10:00 6 | * @Description: webpack打包文件 7 | */ 8 | import path from 'path' 9 | // const merge = require('webpack-merge') 10 | import merge from 'webpack-merge' 11 | const webpackCommon = require('./webpack.config.common') 12 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // 清除上一次打包后的旧版文件 13 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 将css单独打包出来 14 | const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin") // 压缩css,将多余的css注释和重复代码去除 15 | const ROOT_PATH = process.cwd(); // 获取到当前node运行的进程目录 16 | const DIST_PATH = path.resolve(ROOT_PATH, "music"); // 获取到dist目录 17 | const ProgressBarPlugin = require('progress-bar-webpack-plugin') // 显示进度条 18 | const chalk = require('react-dev-utils/chalk') 19 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 20 | const TerserPlugin = require('terser-webpack-plugin'); 21 | import Config from '../web.config' 22 | 23 | module.exports = merge(webpackCommon, { 24 | mode: 'production', 25 | entry: { 26 | build: path.resolve(process.cwd(), 'src/index.tsx'), 27 | vendor: Config.Vendor 28 | }, 29 | output: { 30 | path: DIST_PATH, 31 | filename: "static/js/[name].[hash].js" 32 | }, 33 | module: { 34 | rules: [ 35 | { 36 | test: /\.(sa|sc|c)ss$/, 37 | // 使用 'style-loader','css-loader' 38 | use: [ 39 | { 40 | loader: MiniCssExtractPlugin.loader, 41 | options: { 42 | hmr: process.env.NODE_ENV === 'development', 43 | }, 44 | }, 45 | 'css-loader', 46 | { 47 | loader: 'postcss-loader', 48 | options: { 49 | ident: 'postcss', 50 | plugins: [ 51 | require('autoprefixer'), 52 | ] 53 | } 54 | }, 55 | 'sass-loader', 56 | ] 57 | }, 58 | ] 59 | }, 60 | // configureWebpack: { 61 | performance: { 62 | hints:false 63 | }, 64 | // }, 65 | externals: { // 配置通过第三方cdn引入 66 | }, 67 | optimization: { // 分包 68 | // splitChunks: {//可以在这里直接设置抽离代码的参数,最后将符合条件的代码打包至一个公共文件 69 | // maxInitialRequests: 10, 70 | // cacheGroups: {//设置缓存组用来抽取满足不同规则的chunk,下面以生成common、vender为例 71 | // // 根据不同的参数设置抽取不同条件的公共js 72 | // common: {// 73 | // test: /\.js/,//匹配规则 74 | // name: 'common/common', 75 | // chunks: 'all', 76 | // priority: 1//设置匹配优先级,数字越小,优先级越低 77 | // }, 78 | // vendor: { 79 | // test: /\.js/,//匹配规则 80 | // name: 'vender/vender', 81 | // test: /[\\/]node_modules[\\/]/,//匹配node模块中匹配的的模块 82 | // //priority:10,//设置匹配优先级,数字越大,优先级越高 83 | // chunks: 'all', 84 | // enforce: true //强制生成 85 | // }, 86 | // } 87 | // }, 88 | splitChunks: { 89 | chunks: 'async', 90 | cacheGroups: { 91 | vendors: { 92 | test: /node_modules/, 93 | name: 'vendors', 94 | priority: -10, 95 | }, 96 | common: { 97 | name: 'common', 98 | minChunks: 2, 99 | minSize: 30, 100 | chunks: 'all' 101 | } 102 | } 103 | }, 104 | 105 | minimize: true, 106 | minimizer: [new TerserPlugin()], 107 | }, 108 | plugins: [ 109 | new ProgressBarPlugin({ 110 | format: ' build [:bar] ' + chalk.green.bold(':percent') + ' (:elapsed seconds)', 111 | clear: false 112 | }), 113 | new CleanWebpackPlugin(), // 清除上一次打包后的旧版文件 114 | new MiniCssExtractPlugin({ 115 | // 与webpackoptions.output中相同选项类似的选项 116 | // 两个选项都是可选的 117 | filename: 'static/css/[id].[hash].css', 118 | chunkFilename: 'static/css/[id].[hash].css' 119 | }), 120 | new OptimizeCSSAssetsPlugin(), 121 | new CopyWebpackPlugin([ 122 | { 123 | from: path.resolve(process.cwd(), "public/cloudmusic.js"), 124 | to: path.resolve(process.cwd(), "music"), 125 | ignore: ['.*'] 126 | } 127 | ]) 128 | ] 129 | }) -------------------------------------------------------------------------------- /src/views/PgIndex/index.tsx: -------------------------------------------------------------------------------- 1 | import { NavBar, Icon } from 'antd-mobile'; 2 | import React, { useState, useEffect, useCallback, memo, useRef, ForwardRefExoticComponent } from 'react' 3 | import RouterView from '../../router/routerView' 4 | import PgLeftSlider from '../PgLeftSlider' 5 | import {observer,inject} from 'mobx-react' 6 | import './index.scss' 7 | 8 | 9 | const LeftSilderAddTitle = inject('commonStore')(observer((props) => { 10 | let index: number = sessionStorage.getItem('tabIndex') ? Number(sessionStorage.getItem('tabIndex')) : 1 11 | let [heightlight, setheightlight] = useState(index) 12 | let [changSHstate, setchangSHstate] = useState(false) 13 | 14 | let tabBar = useRef(null) 15 | 16 | 17 | let tabBars: Array = [ 18 | { 19 | title: '我的', 20 | paths: '/index/my' 21 | }, 22 | { 23 | title: '发现', 24 | paths: '/index/fined' 25 | }, 26 | { 27 | title: '云村', 28 | paths: '/index/firends' 29 | }, 30 | { 31 | title: '视频', 32 | paths: '/index/vidio' 33 | } 34 | ] 35 | 36 | 37 | // 显示侧边栏 38 | const show = useCallback((): void => { 39 | setchangSHstate(true) 40 | }, [changSHstate]) 41 | 42 | // 关闭侧边栏 43 | const hide = useCallback((): void =>{ 44 | setchangSHstate(false) 45 | }, [changSHstate]) 46 | 47 | 48 | // eslint 并不了解你的规则,应该在此处禁用eslint 49 | /* eslint-disable */ 50 | useEffect((): void => { 51 | let index = sessionStorage.getItem('tabIndex') || 1 52 | props.history.push(tabBars[index].paths) 53 | props.commonStore.setTabBarHeight(tabBar.current.offsetHeight) 54 | }, []) 55 | 56 | 57 | // 切换tab模块 58 | function toTabable(path, index): void { 59 | sessionStorage.setItem('tabIndex', index) 60 | setheightlight(index) 61 | props.history.push(path) 62 | } 63 | 64 | return( 65 | <> 66 |
67 | } 70 | rightContent={[ 71 | { 72 | props.history.push('/search') 73 | }} />, 74 | ]} 75 | onLeftClick={show} 76 | > 77 | { 78 | tabBars.map((item, index) => { 79 | return ( 80 |
{ 81 | toTabable(item.paths, index) 82 | }} key={index}>{item.title}
83 | ) 84 | }) 85 | } 86 |
87 |
88 |
{hide()}} 94 | > 95 |
{e.stopPropagation()}} > 96 | 97 |
98 |
99 | 100 | ) 101 | })) 102 | 103 | 104 | const LeftSilderAddTitlepro: ForwardRefExoticComponent = memo(LeftSilderAddTitle) 105 | const RouterViewPro: ForwardRefExoticComponent = memo(RouterView) 106 | 107 | function Index(props: any): JSX.Element { 108 | return ( 109 | <> 110 |
111 | 112 |
113 | 114 |
115 |
116 | 117 | ); 118 | } 119 | 120 | export default Index -------------------------------------------------------------------------------- /src/views/PgRankingList/singleRowData.ts: -------------------------------------------------------------------------------- 1 | const Singlerowdata: Array = [ 2 | { 3 | title: '官方榜', 4 | datalist: [ 5 | { 6 | name: '云音乐飙升榜', 7 | pic: 'https://p1.music.126.net/N2HO5xfYEqyQ8q6oxCw8IQ==/18713687906568048.jpg', 8 | id: '19723756' 9 | }, 10 | { 11 | name: '云音乐新歌榜', 12 | pic: 'https://p1.music.126.net/N2HO5xfYEqyQ8q6oxCw8IQ==/18713687906568048.jpg', 13 | id: '3779629' 14 | }, 15 | { 16 | name: '云音乐热歌榜', 17 | pic: 'https://p1.music.126.net/GhhuF6Ep5Tq9IEvLsyCN7w==/18708190348409091.jpg', 18 | id: '3778678' 19 | }, 20 | { 21 | name: '网易原创歌曲榜', 22 | pic: 'https://p1.music.126.net/sBzD11nforcuh1jdLSgX7g==/18740076185638788.jpg', 23 | id: '2884035' 24 | }, 25 | ] 26 | }, 27 | { 28 | title: '推荐榜', 29 | datalist: [ 30 | { 31 | name: '云音乐说唱榜', 32 | pic: 'https://p1.music.126.net/EJyXfGYsiHxxxoCiTAz6Kg==/109951165611553137.jpg', 33 | id: '991319590' 34 | }, 35 | { 36 | name: '云音乐电音榜', 37 | pic: 'https://p1.music.126.net/FUTBe-hYwYZ7kidXNz2f0g==/109951165611546880.jpg', 38 | id: '1978921795' 39 | }, 40 | { 41 | name: '网络热歌榜', 42 | pic: 'https://p1.music.126.net/k0yGVnDaTLeDe6-rbZahJA==/109951165911602412.jpg', 43 | id: '6723173524' 44 | }, 45 | { 46 | name: '云音乐ACG音乐榜', 47 | pic: 'https://p1.music.126.net/pAFaa_EVMj0ktkDo381dnw==/109951165611551680.jpg', 48 | id: '71385702' 49 | } 50 | ] 51 | }, 52 | { 53 | title: '全球榜', 54 | datalist: [ 55 | { 56 | name: '美国Billboard周榜', 57 | pic: 'https://p1.music.126.net/rwRsVIJHQ68gglhA6TNEYA==/109951165611413732.jpg', 58 | id: '60198' 59 | }, 60 | { 61 | name: 'UK排行榜周榜', 62 | pic: 'https://p1.music.126.net/fhAqiflLy3eU-ldmBQByrg==/109951165613082765.jpg', 63 | id: '180106' 64 | }, 65 | { 66 | name: 'Beatport全球电子舞曲榜', 67 | pic: 'https://p1.music.126.net/oT-RHuPBJiD7WMoU7WG5Rw==/109951166093489621.jpg', 68 | id: '3812895' 69 | }, 70 | { 71 | name: '日本Oricon周榜', 72 | pic: 'https://p1.music.126.net/aXUPgImt8hhf4cMUZEjP4g==/109951165611417794.jpg', 73 | id: '60131' 74 | } 75 | ] 76 | }, 77 | { 78 | title: '更多榜单', 79 | datalist: [ 80 | { 81 | name: '云音乐古典音乐榜', 82 | pic: 'https://p1.music.126.net/7XQ7j5GKsiWQ6hLMtjGGKQ==/109951165611553527.jpg', 83 | id: '71384707' 84 | }, 85 | { 86 | name: '云音乐韩语榜', 87 | pic: 'https://p1.music.126.net/v--zfW0Y0jbexl3CiALGlw==/109951165611550672.jpg', 88 | id: '745956260' 89 | }, 90 | { 91 | name: '云音乐欧美热歌榜', 92 | pic: 'https://p1.music.126.net/uN5PupU0rXEuMq0HIlxb_w==/109951165752868980.jpg', 93 | id: '2809513713' 94 | }, 95 | { 96 | name: '云音乐ACG游戏榜', 97 | pic: 'https://p1.music.126.net/hivOOHMwEmnn9s_6rgZwEQ==/109951164432303700.jpg', 98 | id: '3001795926' 99 | }, 100 | { 101 | name: '云音乐ACG VOCALOID榜', 102 | pic: 'https://p1.music.126.net/Ag7RyRCYiINcd9EtRXf6xA==/109951164432303690.jpg', 103 | id: '3001890046' 104 | }, 105 | { 106 | name: '潜力爆款榜', 107 | pic: 'https://p1.music.126.net/Mi4QPklg1mtbWAfq74tEqQ==/109951165498334721.jpg', 108 | id: '5338990334' 109 | }, 110 | ] 111 | } 112 | ] 113 | 114 | export default Singlerowdata -------------------------------------------------------------------------------- /src/views/PgVidio/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .vidio-wraps { 3 | width: 100%; 4 | height: 100%; 5 | background: gainsboro; 6 | display: flex; 7 | flex-direction: column; 8 | // overflow: hidden; 9 | overflow-y: auto; 10 | .vidio-top-tag { 11 | width: 100%; 12 | height: px2rem(100); 13 | background: #fff; 14 | display: flex; 15 | justify-content: space-between; 16 | align-items: center; 17 | padding: 0 px2rem(60); 18 | .vidio-top-tag-tip { 19 | height: 100%; 20 | line-height: px2rem(100); 21 | text-align: center; 22 | // background: #fff; 23 | position: relative; 24 | .line { 25 | width: 100%; 26 | height: px2rem(5); 27 | // background: red; 28 | left: 0; 29 | position: absolute; 30 | bottom: px2rem(10); 31 | } 32 | } 33 | } 34 | .vidio-contant-list { 35 | width: 100%; 36 | flex: 1; 37 | // background: red; 38 | background: #fff; 39 | overflow-y: auto; 40 | .vidio-list-tip { 41 | width: 95%; 42 | margin: 0 auto; 43 | height: px2rem(700); 44 | margin-bottom: px2rem(15); 45 | margin-top: px2rem(10); 46 | 47 | .video-container { 48 | width: 100%; 49 | height: px2rem(500); 50 | // background: yellow; 51 | border-radius: px2rem(20); 52 | overflow: hidden; 53 | position: relative; 54 | .start { 55 | width: px2rem(52); 56 | height: px2rem(52); 57 | top: 50%; 58 | left: 50%; 59 | margin-left: px2rem(-25); 60 | margin-top: px2rem(-25); 61 | position: absolute; 62 | opacity: .7; 63 | .video-icon { 64 | color: #fff; 65 | font-size: px2rem(80); 66 | } 67 | } 68 | img{ 69 | width: 100%; 70 | height: 100%; 71 | } 72 | .vidio-wrap { 73 | width: 100%; 74 | height: 100%; 75 | overflow: hidden; 76 | .vidio-wrap-constur { 77 | width: 100%!important; 78 | height: 100%!important; 79 | padding: 0!important; 80 | .video-react-video { 81 | width: 100% !important; 82 | height: 100% !important; 83 | padding: 0 !important; 84 | } 85 | } 86 | } 87 | } 88 | .video-name { 89 | width: 100%; 90 | height: px2rem(100); 91 | // background: #ccc; 92 | line-height: px2rem(100); 93 | overflow: hidden; 94 | text-overflow:ellipsis; 95 | white-space: nowrap; 96 | } 97 | .video-info { 98 | width: 100%; 99 | height: px2rem(100); 100 | // background: gold; 101 | display: flex; 102 | justify-content: flex-start; 103 | align-items: center; 104 | .info-header { 105 | width: px2rem(60); 106 | height: px2rem(60); 107 | border-radius: 50%; 108 | overflow: hidden; 109 | // background: green; 110 | img{ 111 | width: 100%; 112 | height: 100%; 113 | } 114 | } 115 | .info-name { 116 | flex: 1; 117 | height: px2rem(60); 118 | line-height: px2rem(60); 119 | padding-left: px2rem(20); 120 | } 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /public/cloudmusic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dream on 2017/5/2. 3 | */ 4 | "use strict"; 5 | //加载所需要的模块 6 | var http = require('http'); 7 | var url = require('url'); 8 | var fs = require('fs'); 9 | var path = require('path'); 10 | var cp = require('child_process'); 11 | 12 | //创建服务 13 | var httpServer = http.createServer(processRequest); 14 | 15 | var port = 3007; 16 | 17 | //指定一个监听的接口 18 | httpServer.listen(port, function() { 19 | console.log(`app is running at port:${port}`); 20 | console.log(`url: http://localhost:${port}`); 21 | cp.exec(`explorer http://localhost:${port}`, function() {}); 22 | }); 23 | 24 | //响应请求的函数 25 | function processRequest(request, response) { 26 | //mime类型 27 | var mime = { 28 | "css": "text/css", 29 | "gif": "image/gif", 30 | "html": "text/html", 31 | "ico": "image/x-icon", 32 | "jpeg": "image/jpeg", 33 | "jpg": "image/jpeg", 34 | "js": "text/javascript", 35 | "json": "application/json", 36 | "pdf": "application/pdf", 37 | "png": "image/png", 38 | "svg": "image/svg+xml", 39 | "swf": "application/x-shockwave-flash", 40 | "tiff": "image/tiff", 41 | "txt": "text/plain", 42 | "wav": "audio/x-wav", 43 | "wma": "audio/x-ms-wma", 44 | "wmv": "video/x-ms-wmv", 45 | "xml": "text/xml" 46 | }; 47 | 48 | //request里面切出标识符字符串 49 | var requestUrl = request.url; 50 | //url模块的parse方法 接受一个字符串,返回一个url对象,切出来路径 51 | var pathName = url.parse(requestUrl).pathname; 52 | 53 | //对路径解码,防止中文乱码 54 | var pathName = decodeURI(pathName); 55 | 56 | //解决301重定向问题,如果pathname没以/结尾,并且没有扩展名 57 | if (!pathName.endsWith('/') && path.extname(pathName) === '') { 58 | pathName += '/'; 59 | var redirect = "http://" + request.headers.host + pathName; 60 | response.writeHead(301, { 61 | location: redirect 62 | }); 63 | //response.end方法用来回应完成后关闭本次对话,也可以写入HTTP回应的具体内容。 64 | response.end(); 65 | } 66 | 67 | //获取资源文件的绝对路径 68 | var filePath = path.resolve(__dirname + pathName); 69 | 70 | if (filePath.indexOf('\active') != -1) { 71 | console.log(20) 72 | // return 73 | // console.log(filePath) 74 | return 75 | } 76 | console.log(filePath, '文件路径'); 77 | //获取对应文件的文档类型 78 | //我们通过path.extname来获取文件的后缀名。由于extname返回值包含”.”,所以通过slice方法来剔除掉”.”, 79 | //对于没有后缀名的文件,我们一律认为是unknown。 80 | var ext = path.extname(pathName); 81 | ext = ext ? ext.slice(1) : 'unknown'; 82 | 83 | //未知的类型一律用"text/plain"类型 84 | var contentType = mime[ext] || "text/plain"; 85 | 86 | fs.stat(filePath, (err, stats) => { 87 | if (err) { 88 | response.writeHead(404, { "content-type": "text/html" }); 89 | response.end("

404 Not Found

"); 90 | } 91 | //没出错 并且文件存在 92 | if (!err && stats.isFile()) { 93 | readFile(filePath, contentType); 94 | } 95 | //如果路径是目录 96 | if (!err && stats.isDirectory()) { 97 | var html = "
    "; 98 | //读取该路径下文件 99 | fs.readdir(filePath, (err, files) => { 100 | if (err) { 101 | console.log("读取路径失败!"); 102 | } else { 103 | //做成一个链接表,方便用户访问 104 | var flag = false; 105 | for (var file of files) { 106 | //如果在目录下找到index.html,直接读取这个文件 107 | if (file === "index.html") { 108 | readFile(filePath + (filePath[filePath.length - 1] == '/' ? '' : '/') + 'index.html', "text/html"); 109 | flag = true; 110 | break; 111 | }; 112 | html += `
  • ${file}
  • `; 113 | } 114 | if (!flag) { 115 | html += '
'; 116 | response.writeHead(200, { "content-type": "text/html" }); 117 | response.end(html); 118 | } 119 | } 120 | }); 121 | } 122 | 123 | //读取文件的函数 124 | function readFile(filePath, contentType) { 125 | response.writeHead(200, { "content-type": contentType }); 126 | //建立流对象,读文件 127 | var stream = fs.createReadStream(filePath); 128 | //错误处理 129 | stream.on('error', function() { 130 | response.writeHead(500, { "content-type": contentType }); 131 | response.end("

500 Server Error

"); 132 | }); 133 | //读取文件 134 | stream.pipe(response); 135 | } 136 | }); 137 | } -------------------------------------------------------------------------------- /src/views/PgRadioList/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react'; 2 | import './index.scss' 3 | import {Headers, Icons} from 'components/index' 4 | import { apidjhotlist } from 'api' 5 | function PgRadioList(props: any): JSX.Element { 6 | 7 | let [radiolist, setradiolist] = useState([]) 8 | let [currentSelect, setcurrentSelect] = useState(0) 9 | const getapidjhotlist = useCallback((type) => { 10 | let params = { 11 | type: type 12 | } 13 | apidjhotlist(params).then(res => { 14 | setradiolist(res.toplist) 15 | }) 16 | }, []) 17 | 18 | useEffect(() => { 19 | getapidjhotlist('new') 20 | }, []) 21 | 22 | const checkTab = useCallback((type) => { 23 | if (type == currentSelect) { 24 | return; 25 | } 26 | setcurrentSelect(type) 27 | type ? getapidjhotlist('hot') : getapidjhotlist('new') 28 | 29 | }, [currentSelect]) 30 | 31 | return <> 32 |
33 | 电台排行榜 34 |
35 |
{ 36 | checkTab(0) 37 | }}> 38 | 新晋电台榜 39 |
40 |
41 |
{ 42 | checkTab(1) 43 | }}> 44 | 热门电台榜 45 |
46 |
47 |
48 |
49 | { 50 | radiolist.map((item, index) => { 51 | if (index == 0 || index ==1 || index == 2) { 52 | return ( 53 |
{ 54 | props.history.push(`/radiodetails?id=${item.id}`) 55 | }}> 56 |
57 | {index + 1} 58 |
59 |
60 | 61 |
62 |
63 |
64 | {item.name} 65 |
66 |
67 | {item.creatorName} 68 |
69 |
70 | {item.subCount} 71 |
72 |
73 |
74 | ) 75 | } else { 76 | return ( 77 |
{ 78 | props.history.push(`/radiodetails?id=${item.id}`) 79 | }}> 80 |
81 | {index + 1} 82 |
83 |
84 | 85 |
86 |
87 |
88 | {item.name} 89 |
90 |
91 | {item.creatorName} 92 |
93 |
94 | {item.subCount} 95 |
96 |
97 |
98 | ) 99 | } 100 | 101 | }) 102 | } 103 | 104 |
105 |
106 | 107 | } 108 | 109 | export default PgRadioList -------------------------------------------------------------------------------- /src/views/PgMy/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | .my-music-wraps { 3 | width: 100%; 4 | height: 100%; 5 | overflow-y: auto; 6 | .my-music-top-wrap { 7 | width: 100%; 8 | height: px2rem(200); 9 | overflow-x: auto; 10 | &::-webkit-scrollbar { 11 | display: none; 12 | } 13 | .my-music-top-content { 14 | height: 100%; 15 | display: flex; 16 | justify-content: space-between; 17 | padding: 0 px2rem(40); 18 | align-items: center; 19 | overflow-x: auto; 20 | &::-webkit-scrollbar { 21 | display: none; 22 | } 23 | .my-music-top-content-wrap { 24 | width: px2rem(120); 25 | height: px2rem(130); 26 | .my-music-top-tip-title { 27 | font-size: px2rem(25); 28 | padding-top: px2rem(20); 29 | text-align: center; 30 | } 31 | } 32 | .my-music-top-tip { 33 | width: px2rem(90); 34 | height: px2rem(90); 35 | background: red; 36 | border-radius: 50%; 37 | // margin-left: px2rem(40); 38 | margin: 0 auto; 39 | flex-shrink: 0; 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | .fm-icon { 44 | font-size: px2rem(60); 45 | color: #fff; 46 | } 47 | .love-icon { 48 | font-size: px2rem(50); 49 | color: #fff; 50 | } 51 | .radio-icon { 52 | font-size: px2rem(50); 53 | color: #fff; 54 | } 55 | .run-icon { 56 | font-size: px2rem(50); 57 | color: #fff; 58 | } 59 | .drive-icon { 60 | font-size: px2rem(60); 61 | color: #fff; 62 | } 63 | } 64 | } 65 | } 66 | .my-music-list-config-wrap { 67 | width: 100%; 68 | .my-music-list-config-tip { 69 | width: 100%; 70 | height: px2rem(110); 71 | // background: #ccc; 72 | display: flex; 73 | justify-content: flex-start; 74 | align-items: center; 75 | padding:0 px2rem(30); 76 | border-bottom: px2rem(1) solid gainsboro; 77 | .local-icon { 78 | font-size: px2rem(50); 79 | color: #000; 80 | } 81 | .my-music-list-config-title { 82 | color: #000; 83 | padding-left: px2rem(40); 84 | font-size: px2rem(30) 85 | } 86 | } 87 | } 88 | .my-music-line { 89 | width: 100%; 90 | height: px2rem(20); 91 | background: gainsboro; 92 | } 93 | .my-music-list-create { 94 | .my-music-list-create-title { 95 | width: 100%; 96 | padding: px2rem(20) px2rem(40); 97 | font-weight: 800; 98 | text-align: left; 99 | color: #000; 100 | } 101 | .no-order { 102 | width: 80%; 103 | margin: 0 auto; 104 | height: px2rem(120); 105 | // background: #ccc; 106 | border: px2rem(3) dashed #ccc; 107 | border-radius: px2rem(20); 108 | text-align: center; 109 | line-height: px2rem(120); 110 | } 111 | .my-music-list-create-tip { 112 | width: 100%; 113 | height: px2rem(150); 114 | // background: #ccc; 115 | display: flex; 116 | justify-content: flex-start; 117 | align-items: center; 118 | padding:0 px2rem(20); 119 | .my-music-list-create-tip-pic { 120 | width: px2rem(130); 121 | height: px2rem(130); 122 | // background: lightblue; 123 | background: url('../../assets/images/timg.gif') no-repeat; 124 | background-size: 100% 100%; 125 | border-radius: px2rem(10); 126 | img { 127 | width: 100%; 128 | height: 100%; 129 | } 130 | } 131 | .my-music-list-create-tip-content { 132 | flex: 1; 133 | display: flex; 134 | flex-direction: column; 135 | padding:0 px2rem(30); 136 | // justify-content: space-between; 137 | .my-music-list-create-tip-name { 138 | text-align: left; 139 | color: #000; 140 | } 141 | .my-music-list-create-tip-count{ 142 | text-align: left; 143 | color: #666; 144 | padding-top:px2rem(20); 145 | font-size: px2rem(25); 146 | } 147 | 148 | } 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-08-09 14:32:22 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-17 11:58:26 6 | * @Description: 7 | */ 8 | import React from 'react' 9 | export default { 10 | config: [ 11 | { 12 | path: "/home", 13 | component: React.lazy(() => import('../views/PgHome')), 14 | exact: true, 15 | meta: { 16 | isGloblePlayer: false 17 | } 18 | }, 19 | { 20 | path: "/loginphone", 21 | component: React.lazy(() => import('../views/PgLogin/PgPhoneInput')), 22 | exact: true, 23 | meta: { 24 | isGloblePlayer: false 25 | } 26 | }, 27 | { 28 | path: "/loginpassword", 29 | component: React.lazy(() => import('../views/PgLogin/PgPasswordInput')), 30 | exact: true, 31 | meta: { 32 | isGloblePlayer: false 33 | } 34 | }, 35 | { 36 | path: "/wrong", 37 | component: React.lazy(() => import('../views/PgWrong')), 38 | exact: true, 39 | meta: { 40 | isGloblePlayer: false 41 | } 42 | }, 43 | { 44 | path: "/mvdetails", 45 | component: React.lazy(() => import('../views/PgMvDetails')), 46 | exact: false, 47 | meta: { 48 | isGloblePlayer: false 49 | } 50 | }, 51 | { 52 | path: "/playdetails", 53 | component: React.lazy(() => import('../views/PgPlayDetails')), 54 | exact: false, 55 | meta: { 56 | isGloblePlayer: true 57 | } 58 | }, 59 | { 60 | path: "/musicplayer", 61 | component: React.lazy(() => import('../views/PgMusicPlayer')), 62 | exact: false, 63 | meta: { 64 | isGloblePlayer: false 65 | } 66 | }, 67 | { 68 | path: "/about", 69 | component: React.lazy(() => import('../views/PgAbout')), 70 | exact: false, 71 | meta: { 72 | isGloblePlayer: true 73 | } 74 | }, 75 | { 76 | path: "/search", 77 | component: React.lazy(() => import('../views/PgSearch')), 78 | exact: false, 79 | meta: { 80 | isGloblePlayer: true 81 | } 82 | }, 83 | { 84 | path: "/yuncun", 85 | component: React.lazy(() => import('../views/PgYunCun')), 86 | exact: false, 87 | meta: { 88 | isGloblePlayer: false 89 | } 90 | }, 91 | { 92 | path: "/recommendeddaily", 93 | component: React.lazy(() => import('../views/PgRecommendedDaily')), 94 | exact: false, 95 | meta: { 96 | isGloblePlayer: true 97 | } 98 | }, 99 | { 100 | path: "/songsquare", 101 | component: React.lazy(() => import('../views/PgSongSquare')), 102 | exact: false, 103 | meta: { 104 | isGloblePlayer: true 105 | } 106 | }, 107 | { 108 | path: "/rankinglist", 109 | component: React.lazy(() => import('../views/PgRankingList')), 110 | exact: false, 111 | meta: { 112 | isGloblePlayer: true 113 | } 114 | }, 115 | { 116 | path: "/radiostation", 117 | component: React.lazy(() => import('../views/PgRadioStation')), 118 | exact: false, 119 | meta: { 120 | isGloblePlayer: true 121 | } 122 | }, 123 | { 124 | path: "/radiodetails", 125 | component: React.lazy(() => import('../views/PgRadioDetails')), 126 | exact: false, 127 | meta: { 128 | isGloblePlayer: true 129 | } 130 | }, 131 | { 132 | path: "/radiolist", 133 | component: React.lazy(() => import('../views/PgRadioList')), 134 | exact: false, 135 | meta: { 136 | isGloblePlayer: true 137 | } 138 | }, 139 | { 140 | path: "/index", 141 | component: React.lazy(() => import('../views/PgIndex')), 142 | exact: false, 143 | meta: { 144 | isGloblePlayer: true 145 | }, 146 | children: [ 147 | { 148 | path: "/index/fined", 149 | component: React.lazy(() => import('../views/PgFind')), 150 | exact: true, 151 | meta: { 152 | isGloblePlayer: true 153 | } 154 | }, 155 | { 156 | path: "/index/my", 157 | component: React.lazy(() => import('../views/PgMy')), 158 | exact: true, 159 | meta: { 160 | isGloblePlayer: true 161 | } 162 | }, 163 | { 164 | path: "/index/firends", 165 | component: React.lazy(() => import('../views/PgFirends')), 166 | exact: true, 167 | meta: { 168 | isGloblePlayer: true 169 | } 170 | }, 171 | { 172 | path: "/index/vidio", 173 | component: React.lazy(() => import('../views/PgVidio')), 174 | exact: true, 175 | meta: { 176 | isGloblePlayer: false 177 | } 178 | } 179 | ] 180 | }, 181 | ] 182 | } -------------------------------------------------------------------------------- /src/views/PgSongSquare/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .song-square-wrap { 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | 9 | .song-square-nav { 10 | width: 100%; 11 | flex: 1; 12 | 13 | .tab-contant { 14 | width: 100%; 15 | height: 100%; 16 | 17 | @media screen and (max-width:400px) { 18 | .recommended-song-list { 19 | width: 100%; 20 | // height: px2rem(400); 21 | display: flex; 22 | justify-content: space-between; 23 | align-items: center; 24 | flex-wrap: wrap; 25 | padding: px2rem(10) px2rem(20); 26 | 27 | .recommended-song-tip { 28 | width: px2rem(250); 29 | height: px2rem(330); 30 | // background: #fff; 31 | margin-top: px2rem(30); 32 | 33 | .recommended-song-price { 34 | width: px2rem(250); 35 | height: px2rem(250); 36 | // background: lightblue; 37 | background: url('../../assets/images/timg.gif') no-repeat; 38 | // background: url('https://wimg.588ku.com/gif620/19/09/03/2ac4df3fe74e781eee46f34e8252ef32.gif') no-repeat; 39 | background-size: 100% 100%; 40 | border-radius: px2rem(10); 41 | overflow: hidden; 42 | 43 | img { 44 | width: 100%; 45 | height: 100%; 46 | } 47 | } 48 | 49 | .recommended-song-text { 50 | width: 100%; 51 | font-size: px2rem(25); 52 | padding-top: px2rem(10); 53 | text-align: left; 54 | color: #000; 55 | word-break: break-all; 56 | margin: 0; 57 | } 58 | } 59 | } 60 | } 61 | 62 | @media screen and (min-width:400px) { 63 | .recommended-song-list { 64 | width: 100%; 65 | // height: px2rem(400); 66 | display: flex; 67 | justify-content: space-between; 68 | align-items: center; 69 | flex-wrap: wrap; 70 | padding: px2rem(10) px2rem(40); 71 | 72 | .recommended-song-tip { 73 | width: px2rem(280); 74 | height: px2rem(350); 75 | margin-bottom: px2rem(30); 76 | 77 | .recommended-song-price { 78 | width: px2rem(280); 79 | height: px2rem(280); 80 | background: url('../../assets/images/timg.gif') no-repeat; 81 | background-size: 100% 100%; 82 | border-radius: px2rem(10); 83 | overflow: hidden; 84 | 85 | img { 86 | width: 100%; 87 | height: 100%; 88 | } 89 | } 90 | 91 | .recommended-song-text { 92 | width: 100%; 93 | font-size: px2rem(25); 94 | padding-top: px2rem(20); 95 | text-align: left; 96 | color: #000; 97 | word-break: break-all; 98 | margin: 0; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | .song-square-swiper-wrap { 106 | width: 100%; 107 | height: px2rem(520); 108 | margin-top: px2rem(40); 109 | margin-bottom: px2rem(20); 110 | 111 | .swiper-container { 112 | width: 100%; 113 | overflow: unset; 114 | } 115 | 116 | .swiper-wrapper { 117 | margin: 0 auto!important; 118 | height: 100%; 119 | } 120 | .swiper-slide { 121 | width: 60%; 122 | height:100%; 123 | height: px2rem(500); 124 | border-radius: px2rem(15); 125 | .cursol-pic { 126 | width: px2rem(400); 127 | margin: 0 auto; 128 | border-radius: px2rem(15) px2rem(15) px2rem(0) px2rem(0); 129 | height: px2rem(400); 130 | overflow: hidden; 131 | box-shadow: 2px 0px 6px 2px #ccc; 132 | img{ 133 | width: 100%; 134 | height: 100%; 135 | } 136 | 137 | } 138 | .cursol-name { 139 | width: px2rem(400); 140 | height: px2rem(100); 141 | padding: px2rem(20); 142 | margin: 0 auto; 143 | box-shadow: 2px 2px 6px 2px #ccc; 144 | border-radius: px2rem(0) px2rem(0) px2rem(15) px2rem(15); 145 | overflow: hidden; 146 | text-overflow: ellipsis; 147 | line-clamp: 2; 148 | -webkit-line-clamp: 2; 149 | -webkit-box-orient: vertical; 150 | display: -webkit-box; 151 | } 152 | 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/store/modules/playerStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2021-08-03 16:12:39 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-16 16:56:24 6 | * @Descripttion: 播放器 【独立 / 全局 共享】 7 | */ 8 | import { observable, action, runInAction } from 'mobx' 9 | import { apisongurl, apisongdetail, apilyric } from "api"; 10 | 11 | 12 | enum typeCheck { 13 | add, 14 | last 15 | } 16 | class PlayerStore { 17 | @observable playerState: boolean = false 18 | @observable currentSongUrl: string = "" 19 | @observable songDetail: any = null 20 | @observable audiosRef: React.MutableRefObject = null 21 | @observable songId: string = sessionStorage.getItem("songId") || "" 22 | @observable songListIndex: number = 0 23 | 24 | @observable songType = sessionStorage.getItem("songType") || "" 25 | 26 | @observable songCurrentTime = "00:00" 27 | @observable songTotalTime = "00:00" 28 | 29 | // 修改播放器状态 30 | @action.bound 31 | handlePlayerState(state: boolean): void { 32 | this.playerState = state 33 | } 34 | 35 | // 获取歌曲详情 36 | async handleGetSongDetail(): Promise { 37 | try { 38 | let res = await apisongdetail({ id: this.songId }) 39 | runInAction(() => { 40 | this.songDetail = res.songs[0] 41 | }) 42 | 43 | } catch (error) { 44 | console.log(error) 45 | } 46 | } 47 | 48 | // 获取歌曲播放链接 49 | @action.bound 50 | async handleGetSongUrl(): Promise { 51 | try { 52 | let res: any = await apisongurl({ id: this.songId }) 53 | this.currentSongUrl = res.data[0].url 54 | this.handlePlayerState(true) 55 | } catch (error) { 56 | console.log(error) 57 | } 58 | } 59 | 60 | // 获取播放器dom元素属性 61 | @action.bound 62 | handleGetAudioRef(ref: React.MutableRefObject) { 63 | this.audiosRef = ref; 64 | } 65 | 66 | // 获取当前播放歌曲id 67 | @action.bound 68 | handleGetSongId(id: string) { 69 | console.log(id) 70 | this.songId = id 71 | sessionStorage.setItem("songId", id) 72 | } 73 | 74 | // 重置歌曲 | 初始化歌曲 75 | @action.bound 76 | async handleInitSong() { 77 | this.audiosRef.current.pause(); 78 | this.handlePlayerState(false); 79 | try { 80 | 81 | await this.handleGetSongUrl() 82 | await this.handleGetSongDetail() 83 | } catch (error) { 84 | console.error(error) 85 | } 86 | } 87 | 88 | // 修改当前songIndex 89 | @action.bound 90 | handleSongIndex(index: number) { 91 | this.songListIndex = index 92 | } 93 | 94 | // 切换歌曲 ===== 【上一首 | 下一首】 95 | @action.bound 96 | async handleSwitchSongs(type) { 97 | let list = JSON.parse(sessionStorage.getItem("songListDetails")); 98 | let okid = this.songType == "radio" ? "mainTrackId" : "id"; 99 | if (type === typeCheck[1]) { 100 | this.songListIndex = this.songListIndex - 1; 101 | } else { 102 | this.songListIndex = this.songListIndex + 1; 103 | } 104 | if (this.songListIndex < 0) { 105 | this.songListIndex = 0; 106 | return; 107 | } 108 | if (this.songListIndex > list.length) { 109 | this.songListIndex = list.length; 110 | return; 111 | } 112 | this.audiosRef.current.pause(); 113 | this.handlePlayerState(false); 114 | this.handleGetSongId(list[this.songListIndex][okid]) 115 | await this.handleInitSong() 116 | setTimeout(() => { 117 | this.handlePlayerState(true); 118 | }, 1000); 119 | } 120 | 121 | // 实时获取歌曲播放进度 122 | @action.bound 123 | handleSongCurrentTime() { 124 | this.songCurrentTime = this.audiosRef.current.currentTime; 125 | } 126 | 127 | @action.bound 128 | handleSongTotalTime(time: string) { 129 | this.songTotalTime = time 130 | } 131 | 132 | // 自动播放,操作自动切换下一首歌曲 133 | @action.bound 134 | async handlePlayOrPause() { 135 | this.handlePlayerState(false); 136 | this.songCurrentTime = "00:00"; 137 | await this.handleSwitchSongs(typeCheck[0]); 138 | setTimeout(() => { 139 | this.handlePlayerState(true); 140 | }, 1000); 141 | } 142 | 143 | // 操作当前歌曲列表类型 144 | @action.bound 145 | handleSongListType(type: string) { 146 | this.songType = type 147 | sessionStorage.setItem("songType", type) 148 | } 149 | 150 | // 中转代理 151 | @action.bound 152 | handleGetProxy(type) { 153 | let id = this.songId 154 | let list = JSON.parse(sessionStorage.getItem("songListDetails")); 155 | for (let i = 0; i < list.length - 1; i++) { 156 | if (list[i].mainTrackId * 1 === Number(id)) { 157 | switch (type) { 158 | case "pic": 159 | return list[i].coverUrl; 160 | break; 161 | case "name": 162 | return list[i].name; 163 | break; 164 | } 165 | } 166 | } 167 | } 168 | // 获取某些特殊下歌曲名字/图片无法直接获取 169 | @action.bound 170 | handleGetName(songDetail) { 171 | if (songDetail) { 172 | if (songDetail.name) { 173 | return songDetail.name; 174 | } else { 175 | let result = this.handleGetProxy("name"); 176 | return result; 177 | } 178 | } else { 179 | return "加载中..."; 180 | } 181 | } 182 | @action.bound 183 | handleGetPic(songDetail) { 184 | if (songDetail) { 185 | if (songDetail.al.picUrl) { 186 | return songDetail.al.picUrl; 187 | } else { 188 | let result = this.handleGetProxy("pic"); 189 | return result; 190 | } 191 | } else { 192 | return "http://p2.music.126.net/SHElx36maw8L6CIXfiNbFw==/109951164144982394.jpg"; 193 | } 194 | } 195 | 196 | } 197 | 198 | export default new PlayerStore() -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Mr.zheng 3 | * @Date: 2019-08-09 15:00:51 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-16 17:05:45 6 | * @Description: 7 | */ 8 | // import axios from 'axios' 9 | import fetch from 'utils/fetch' 10 | 11 | 12 | 13 | /** 14 | * 查询mv的地址 15 | */ 16 | function mvsetusl() { 17 | return fetch.post('/mv/url?id=5436712') 18 | } 19 | 20 | /** 21 | * 查询视频标签列表 22 | */ 23 | function videoGroup() { 24 | return fetch.post('/video/group?id=9104') 25 | } 26 | 27 | 28 | 29 | 30 | 31 | 32 | /** 33 | * 推荐歌单 34 | */ 35 | function apipersonalizedSongList(params) { 36 | return fetch.post(`/personalized?limit=${params.limit}`, {}) 37 | } 38 | 39 | /** 40 | * 推荐新碟 41 | */ 42 | function apialbum(params) { 43 | return fetch.post(`/album/new?limit=${params.limit}`, {}) 44 | } 45 | 46 | 47 | /** 48 | * 推荐mv 49 | * @param params 50 | */ 51 | function apipersonalizedMv(params) { 52 | return fetch.post(`/personalized/mv?limit=${params.limit}`, {}) 53 | } 54 | 55 | 56 | /** 57 | * 查询歌单详情 58 | */ 59 | 60 | function apiplaylistDetail(params) { 61 | return fetch.post(`/playlist/detail?id=${params.id}`, {}) 62 | } 63 | 64 | /** 65 | * 查询歌曲地址 66 | */ 67 | 68 | function apisongurl(params) { 69 | return fetch.post(`/song/url?id=${params.id}`, {}) 70 | } 71 | 72 | /** 73 | * 查询mv的详情 74 | */ 75 | function apimvdetails(params) { 76 | return fetch.post(`/mv/detail?mvid=${params.id}`, {}) 77 | } 78 | 79 | 80 | /** 81 | * 查询相关视频 82 | */ 83 | function apirelatedAllvideo(params) { 84 | return fetch.post(`/related/allvideo?id=${params.id}`) 85 | } 86 | /** 87 | * 查询相关视频 88 | */ 89 | export function apiMvUrl(params) { 90 | return fetch.post(`/mv/url?id=${params.id}`) 91 | } 92 | 93 | /** 94 | * mv评论 95 | */ 96 | function apicommentMv(params) { 97 | return fetch.post(`/comment/mv?id=${params.id}`) 98 | } 99 | 100 | /** 101 | * 相似mv 102 | */ 103 | function apisimiMv(params) { 104 | return fetch.post(`/simi/mv?mvid=${params.id}`) 105 | } 106 | 107 | /** 108 | * 最新mv 109 | */ 110 | function apifirstMv(params?) { 111 | return fetch.post(`/mv/all?order=最热&area=韩国`) 112 | } 113 | 114 | /** 115 | * 获取用户的歌单哪 116 | */ 117 | function apiuserplayer(params?) { 118 | return fetch.post(`/user/playlist?uid=${params.uid}`, {}) 119 | } 120 | 121 | /** 122 | * 123 | * @param data 124 | */ 125 | function apilogincellphone(params: any) { 126 | return fetch.post(`/login/cellphone?phone=${params.phone}&password=${params.password}`, {}) 127 | } 128 | 129 | /** 130 | * 获取用户的歌单哪 131 | */ 132 | function apisearchhotdetai(params?) { 133 | return fetch.post(`/search/hot/detail`, {}) 134 | } 135 | 136 | 137 | /** 138 | * 收索歌曲 139 | */ 140 | function apisearchsuggest(params?) { 141 | let { keywords, type} = params 142 | return fetch.post(`/search?keywords=${keywords}&type=${type}`, {}) 143 | } 144 | 145 | /** 146 | * 获取歌曲详情 147 | */ 148 | function apisongdetail(params?, signal?) { 149 | return fetch.post(`/song/detail?ids=${params.id}`, {}, signal) 150 | } 151 | 152 | /** 153 | * 获取轮播图数据 154 | */ 155 | function apibanner(params?) { 156 | return fetch.post(`/banner?type=2`, {}) 157 | } 158 | 159 | /** 160 | * 获取云村热评 161 | */ 162 | function apievent(params?) { 163 | // return fetch.post(`/event?pagesize=30`, {}) 164 | return fetch.post('/comment/hotwall/list', {}) 165 | } 166 | 167 | /** 168 | * 获取歌词 169 | */ 170 | function apilyric(params?) { 171 | return fetch.post(`/lyric?id=${params.id}`, {}) 172 | } 173 | 174 | // 获取视频标签 175 | function apivideogroup(params?) { 176 | return fetch.post(`/video/group/list`, {}) 177 | } 178 | 179 | //获取视频标签下的视频 180 | 181 | function apigrouplist(params?) { 182 | return fetch.post(`/video/group?id=${params.id}`, {}) 183 | } 184 | 185 | // 获取视频播放地址 186 | function apivideourl(params?) { 187 | return fetch.post(`/video/url?id=${params.id}`, {}) 188 | } 189 | 190 | // 获取热门评论 191 | function apihotcomment(params?) { 192 | return fetch.post(`/comment/hot?id=${params.id}&type=0`, {}) 193 | } 194 | 195 | // 获取每日推荐歌曲 196 | function apirecommendsongs(params?) { 197 | return fetch.post(`/recommend/songs`, {}) 198 | } 199 | 200 | // 获取歌单分类 201 | function apiplaylistcatlist(params?) { 202 | return fetch.post(`/playlist/hot`, {}) 203 | } 204 | 205 | // 获取每日推荐歌单歌单 206 | function apiresource(params?) { 207 | return fetch.post(`/recommend/resource`, {}) 208 | } 209 | 210 | // 获取分类歌单列表 211 | function apihighqualitylist(params?) { 212 | let { cat, limit, before} = params 213 | return fetch.post(`/top/playlist/highquality?cat=${cat}&limit=${limit}&before=${before}`, {}) 214 | } 215 | 216 | function apitoplist(params) { 217 | return fetch.post(`/top/list?idx=${params.id}`, {}) 218 | } 219 | 220 | export function apiTopListDetail() { 221 | return fetch.post(`/toplist/detail`, {}) 222 | } 223 | 224 | // 获取专辑详情 225 | function apialbumlist(params) { 226 | return fetch.post(`/album?id=${params.id}`, {}) 227 | } 228 | 229 | // 获取电台banner 230 | function apidjbanner(params?) { 231 | return fetch.post(`/dj/banner`, {}) 232 | } 233 | 234 | // 获取电台分类 235 | function apidjcatelist(params?) { 236 | return fetch.post(`/dj/category/recommend`, {}) 237 | } 238 | 239 | // 获取电台推荐 240 | function apipersonalizeddjprogram(params?) { 241 | return fetch.post(`/dj/today/perfered`, {}) 242 | } 243 | 244 | // 获取电台节目列表 245 | function apiprogram(params) { 246 | let { offset } = params 247 | return fetch.post(`/dj/program?rid=${params.id}&limit=40&offset=${offset}`, {}) 248 | } 249 | 250 | // 查询电台详情 251 | function apidjdetail(params?) { 252 | return fetch.post(`/dj/detail?rid=${params.id}`, {}) 253 | } 254 | 255 | 256 | // 查询电台榜单 257 | function apidjhotlist(params) { 258 | let { type } = params 259 | return fetch.post(`/dj/toplist?type=${type}`, {}) 260 | } 261 | 262 | 263 | export { 264 | apipersonalizeddjprogram, 265 | apiplaylistcatlist, 266 | apihighqualitylist, 267 | apilogincellphone, 268 | apirecommendsongs, 269 | apipersonalizedMv, 270 | apiresource, 271 | apidjhotlist, 272 | apiprogram, 273 | apidjbanner, 274 | apidjdetail, 275 | apitoplist, 276 | mvsetusl, 277 | videoGroup, 278 | apidjcatelist, 279 | apirelatedAllvideo, 280 | apicommentMv, 281 | apiplaylistDetail, 282 | apipersonalizedSongList, 283 | apialbum, 284 | apialbumlist, 285 | apisongurl, 286 | apimvdetails, 287 | apisimiMv, 288 | apifirstMv, 289 | apiuserplayer, 290 | apisearchhotdetai, 291 | apisearchsuggest, 292 | apisongdetail, 293 | apibanner, 294 | apievent, 295 | apilyric, 296 | apivideogroup, 297 | apigrouplist, 298 | apivideourl, 299 | apihotcomment 300 | } -------------------------------------------------------------------------------- /src/components/PlayerModule/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zhangzheng 3 | * @Date: 2021-08-03 17:03:22 4 | * @LastEditors: zhangzheng 5 | * @LastEditTime: 2021-08-05 16:22:03 6 | * @Descripttion: 7 | */ 8 | import React, { useState, useEffect, useRef, useCallback } from "react" 9 | import { Headers, Icons, Toasts } from "components/index"; 10 | import { Slider, Modal } from "antd-mobile"; 11 | import { inject, observer } from "mobx-react"; 12 | 13 | 14 | interface ejectModuleProps { 15 | showModule: boolean; 16 | setshowModule: Function; 17 | getsongurl: Function; 18 | setChildModuleScroll: Function; 19 | [propName: string]: any; 20 | } 21 | function EjectModule(props: ejectModuleProps): JSX.Element { 22 | let [songListDetail, setsongListDetail] = useState>([]); 23 | let listRef = useRef(null); 24 | 25 | /** 26 | * 实时获取歌曲在列表的位子【并在切换上一首 | 下一首实时更新】 27 | */ 28 | useEffect((): void => { 29 | let list = JSON.parse(sessionStorage.getItem("songListDetails")); 30 | setsongListDetail(list); 31 | sessionStorage.setItem( 32 | "currScrollTop", 33 | (50 * (props.playerStore.songListIndex - 1)).toString() 34 | ); 35 | listRef.current.scrollTop = 50 * (props.playerStore.songListIndex - 1); 36 | }, [props.playerStore.songListIndex]); 37 | 38 | /** 39 | * 弹层打开时设置歌曲列表scroll高度 40 | * @param scrollCurrent scroll高度 41 | */ 42 | const setScroll = useCallback((scrollCurrent): void => { 43 | if (listRef.current) { 44 | setTimeout(() => { 45 | listRef.current.scrollTop = scrollCurrent; 46 | }, 50); 47 | } 48 | }, []); 49 | 50 | /** 51 | * 设置歌曲列表scroll高度 52 | */ 53 | const setScrollTop = useCallback((): void => { 54 | console.log(props.playerStore.songId, "使用痰喘进行对比的ID") 55 | let list = JSON.parse(sessionStorage.getItem("songListDetails")); 56 | let okId = props.playerStore.songType == "radio" ? "mainTrackId" : "id"; 57 | for (let index = 0; index < list.length; index++) { 58 | if (list[index][okId] == props.playerStore.songId) { 59 | props.playerStore.handleSongIndex(index); 60 | sessionStorage.setItem("currScrollTop", (50 * (index - 1)).toString()); 61 | listRef.current.scrollTop = 50 * (index - 1); 62 | return; 63 | } 64 | } 65 | 66 | 67 | }, []); 68 | 69 | /** 70 | * 实时更新歌曲列表scroll高度 71 | */ 72 | props.setChildModuleScroll(setScroll); 73 | 74 | /** 75 | * 初始化获取歌曲列表scroll高度 76 | */ 77 | useEffect((): void => { 78 | console.log("EjectModule函数"); 79 | setScrollTop(); 80 | }, []); 81 | 82 | /** 83 | * 切换音乐,并更新列表scroll高度 84 | * @param index 歌曲列表的下标 85 | * @param id 歌曲id 86 | */ 87 | const selectMusic = useCallback((index, id): void => { 88 | sessionStorage.setItem("currScrollTop", (50 * (index - 1)).toString()); 89 | props.playerStore.handleInitSong(id); 90 | props.playerStore.handleSongIndex(index); 91 | listRef.current.scrollTop = 50 * (index - 1); 92 | }, []); 93 | return ( 94 | <> 95 |
96 | { 101 | props.setshowModule(false); 102 | }} 103 | animationType="slide-up" 104 | > 105 |
106 |
107 |
108 | 109 | 播放列表 110 | ({songListDetail.length}) 111 |
112 |
113 |
114 | {songListDetail.map((res, index) => { 115 | return ( 116 |
117 |
{ 121 | let okid = props.playerStore.songType == "radio" ? "mainTrackId" : "id"; 122 | selectMusic(index, res[okid]); 123 | }} 124 | > 125 | {props.playerStore.songListIndex === index ? ( 126 | 127 | ) : null} 128 |
129 | {res.name} 130 |
131 | 132 |  -  133 | 134 |
138 | {props.playerStore.songType == "radio" 139 | ? res.radio.name 140 | : res.ar 141 | ? res.ar[0].name 142 | : res.artists[0].name} 143 |
144 |
145 |
146 | ); 147 | })} 148 |
149 |
150 |
151 |
152 | 153 | ); 154 | } 155 | 156 | export default inject("playerStore")(observer(EjectModule)); 157 | -------------------------------------------------------------------------------- /src/views/PgMvDetails/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .mv-details-wraps { 4 | width: 100%; 5 | height: 100%; 6 | background: #fff; 7 | // overflow-y: auto; 8 | overflow: hidden; 9 | display: flex; 10 | flex-direction: column; 11 | .mv-vidio-wrap { 12 | width: 100%; 13 | // height: px2rem(500); 14 | background: lightcoral; 15 | // .mv-vidio-wrap-constur { 16 | // width: 100%; 17 | // padding-top:px2rem(500) !important; 18 | // } 19 | } 20 | .mv-vidio-contains { 21 | width: 100%; 22 | flex: 1; 23 | overflow-y: auto; 24 | -webkit-overflow-scrolling : touch; 25 | } 26 | .mv-vidio-title { 27 | width: 100%; 28 | // height: px2rem(100); 29 | padding: px2rem(20); 30 | font-weight: 800; 31 | // background: #ccc; 32 | } 33 | .mv-vidio-label{ 34 | width: 100%; 35 | display: flex; 36 | justify-content: flex-start; 37 | align-items: center; 38 | padding: px2rem(20) 0; 39 | flex-wrap: wrap; 40 | .playback-volume { 41 | font-size: px2rem(30); 42 | color: #ccc; 43 | margin-left: px2rem(20); 44 | } 45 | .labels-playback { 46 | padding:px2rem(15) px2rem(20); 47 | font-size: px2rem(25); 48 | background:#f0f0f0; 49 | border-radius: px2rem(30); 50 | margin-left: px2rem(20); 51 | } 52 | } 53 | .user-follow { 54 | width: 100%; 55 | height: px2rem(130); 56 | display: flex; 57 | justify-content: space-between; 58 | align-items: center; 59 | padding:0 px2rem(20); 60 | border-top: px2rem(1) solid #f0f0f0; 61 | .vidio-name { 62 | width: 30%; 63 | height: 100%; 64 | display: flex; 65 | justify-content: space-between; 66 | align-items: center; 67 | .header-portrail { 68 | width: px2rem(50); 69 | height: px2rem(50); 70 | background: lightblue; 71 | border-radius: 50%; 72 | overflow: hidden; 73 | img{ 74 | width: 100%; 75 | height: 100%; 76 | } 77 | } 78 | .header-name { 79 | flex: 1; 80 | overflow: hiddden; 81 | text-overflow: ellipsis; 82 | white-space: nowrap; 83 | margin-left: px2rem(30); 84 | } 85 | } 86 | .please-follow { 87 | padding:px2rem(10) px2rem(30); 88 | font-size: px2rem(25); 89 | background: red; 90 | color: #fff; 91 | border-radius: px2rem(30); 92 | } 93 | } 94 | .lines { 95 | width: 100%; 96 | height: px2rem(20); 97 | background: #f0f0f0; 98 | } 99 | .relevant-video-wrap { 100 | width: 100%; 101 | .relevant-video-title { 102 | width: 100%; 103 | height: px2rem(80); 104 | padding-left: px2rem(20); 105 | font-size: px2rem(30); 106 | font-weight: 800; 107 | line-height: px2rem(80); 108 | } 109 | .relevant-video-list { 110 | width: 100%; 111 | height: px2rem(190); 112 | // background: #ccc; 113 | display: flex; 114 | justify-content: space-between; 115 | align-items: center; 116 | padding:0 px2rem(30); 117 | .relevant-video-list-left { 118 | width: px2rem(300); 119 | height: px2rem(150); 120 | background: lightblue; 121 | border-radius: px2rem(15); 122 | overflow: hidden; 123 | img{ 124 | width: 100%; 125 | height: 100%; 126 | } 127 | } 128 | .relevant-video-list-right { 129 | flex: 1; 130 | padding-left: px2rem(30); 131 | display: flex; 132 | justify-content: flex-start; 133 | align-items: center; 134 | .relevant-video-list-title { 135 | font-size: px2rem(30) 136 | } 137 | .relevant-video-list-name { 138 | font-size: px2rem(25); 139 | margin-top:px2rem(15); 140 | color: #ccc; 141 | } 142 | } 143 | } 144 | } 145 | .mv-comment { 146 | width: 100%; 147 | .mv-comment-title { 148 | width: 100%; 149 | height: px2rem(80); 150 | padding-left: px2rem(20); 151 | font-size: px2rem(30); 152 | font-weight: 800; 153 | line-height: px2rem(80); 154 | } 155 | .mv-comment-list { 156 | width: 100%; 157 | border-bottom:px2rem(1) solid #f0f0f0; 158 | padding:px2rem(20) 0; 159 | .mv-comment-list-title{ 160 | width: 100%; 161 | height: px2rem(100); 162 | // background: #ccc; 163 | display: flex; 164 | justify-content: space-between; 165 | align-items: center; 166 | padding:0 px2rem(20); 167 | .mv-comment-list-title-left { 168 | display: flex; 169 | justify-content: space-between; 170 | align-items: center; 171 | padding:0 px2rem(20); 172 | .m-c-l-t-l-header-pic { 173 | width: px2rem(50); 174 | height: px2rem(50); 175 | border-radius: 50%; 176 | background: lightcoral; 177 | overflow: hidden; 178 | img { 179 | width: 100%; 180 | height: 100%; 181 | } 182 | } 183 | .m-c-l-t-l-header-name{ 184 | padding-left: px2rem(20); 185 | // display: fl 186 | .m-c-l-t-l-name { 187 | font-size: px2rem(30) 188 | } 189 | .m-c-l-t-l-time { 190 | font-size: px2rem(25) 191 | } 192 | } 193 | } 194 | .mv-comment-list-title-right { 195 | .mv-comment-likedCount{ 196 | font-size: px2rem(30); 197 | padding-right: px2rem(15) 198 | } 199 | } 200 | } 201 | .mv-comment-list-content { 202 | width: 100%; 203 | // height: px2rem(200); 204 | // background: #ccc; 205 | padding-left: px2rem(100); 206 | padding-right: px2rem(40); 207 | line-height: px2rem(50); 208 | font-size: px2rem(30) 209 | } 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /src/views/PgMvDetails/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react' 2 | import "../../../node_modules/video-react/dist/video-react.css"; 3 | import './index.scss' 4 | import { Icons, Headers } from 'components/index' 5 | import { apimvdetails, apisimiMv, apicommentMv, apiMvUrl } from 'api' 6 | import { Player, BigPlayButton, ControlBar, ReplayControl} from 'video-react'; 7 | import query from 'utils/useQuery'; 8 | 9 | 10 | function PgMvDeatils(props: any): JSX.Element { 11 | let [mvDetails, setmvDetails] = useState(null) 12 | let [relevantVideo, setrelevantVideo] = useState>([]) 13 | let [brilliantComments, setbrilliantComments] = useState>([]) 14 | let [url, setUrl] = useState("") 15 | 16 | useEffect((): void => { 17 | let {id} = query() 18 | getaData(id) 19 | }, []) 20 | 21 | const getaData = async (id): Promise => { 22 | let params = { 23 | id: id 24 | } 25 | await apisimiMv(params).then(res => { 26 | setrelevantVideo(res.mvs) 27 | }) 28 | await apicommentMv(params).then(res => { 29 | setbrilliantComments(res.comments) 30 | }) 31 | let res = await apimvdetails(params) 32 | setmvDetails(res.data) 33 | 34 | let url = await apiMvUrl({id: res.data.id}) 35 | setUrl(url.data.url) 36 | } 37 | 38 | function switchingVideo(id): void { 39 | getaData(id) 40 | props.history.replace(`/mvdetails?id=${id}`) 41 | } 42 | 43 | return( 44 | <> 45 |
46 | {mvDetails ? mvDetails.name : '加载中。。。'} 47 |
48 | 55 | 56 | 57 | 58 | 59 | 60 |
61 |
62 | {mvDetails ? mvDetails.name : ''} 63 |
64 |
65 |
66 | {mvDetails ? mvDetails.likeCount : ''}次观看 67 | { 68 | mvDetails ? mvDetails.artists.map(res => { 69 | return( 70 | {res.name} 71 | ) 72 | }) : null 73 | } 74 |
75 |
76 |
77 |
78 | 79 |
80 |
81 | JSADJS 82 |
83 |
84 |
85 | + 关 注 86 |
87 |
88 |
89 |
90 |
91 | 相关视频 92 |
93 | { 94 | relevantVideo.map((res: any) => { 95 | return( 96 |
{ 97 | switchingVideo(res.id) 98 | }}> 99 |
100 | 101 |
102 |
103 |
104 |

{res.name}

105 |

{res.artists[0].name}

106 |
107 |
108 |
109 | ) 110 | }) 111 | } 112 |
113 |
114 |
115 | 精彩评论 116 |
117 | { 118 | brilliantComments.map(res => { 119 | return( 120 |
121 |
122 |
123 |
124 | 125 |
126 |
127 |

{res.user.nickname}

128 |

1月13日

129 |
130 |
131 |
132 | {res.likedCount} 133 | 134 |
135 |
136 |
137 | {res.content} 138 |
139 |
140 | ) 141 | }) 142 | } 143 |
144 |
145 |
146 | 147 | ) 148 | } 149 | 150 | export default PgMvDeatils -------------------------------------------------------------------------------- /src/views/PgRecommendedDaily/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useCallback, useEffect} from 'react' 2 | import { Icons, Headers } from 'components/index' 3 | import './index.scss' 4 | import { apirecommendsongs } from 'api' 5 | import { inject, observer } from 'mobx-react' 6 | function PgRecommendedDaily(props: any) { 7 | 8 | let [songListDetails, setsongListDetails] = useState>([]) 9 | let [titlePostion, setTitlePostion] = useState(false) 10 | let headerRef = useRef(null) 11 | let titleRef = useRef(null) 12 | 13 | 14 | const getRecommendSongs = useCallback( async (): Promise => { 15 | await apirecommendsongs().then(res => { 16 | let songs = res.data.dailySongs 17 | setsongListDetails(songs) 18 | }) 19 | }, []) 20 | 21 | useEffect((): void => { 22 | getRecommendSongs() 23 | }, []) 24 | 25 | const scrollFun = useCallback((e): void => { 26 | if (e.target.scrollTop > (titleRef.current.offsetHeight - headerRef.current.headerRef.current.offsetHeight)) { 27 | setTitlePostion(true) 28 | } else { 29 | setTitlePostion(false) 30 | } 31 | }, []) 32 | 33 | return <> 34 |
scrollFun(e)}> 35 |
36 |
43 | 44 |
} 63 | className='postion-header' 64 | svgCol={!titlePostion ? '#fff' : ''} 65 | props={props}> 66 | 67 | {'每日推荐'} 68 | 69 |
70 | 71 |
72 | {/* */} 73 |
74 |
75 | {/*
{songListObj ? songListObj.name : ''}
*/} 76 |
77 | {/*
78 | 79 |
*/} 80 |
81 | {/* {songListObj ? songListObj.nickname : ''} */} 82 |
83 |
84 |
85 | {/* {songListObj ? songListObj.description : ''} */} 86 |
87 |
88 |
89 |
90 |
91 |
96 |
{ 97 | props.commonStore.getSongListDetails(songListDetails) 98 | sessionStorage.setItem('songListDetails', JSON.stringify(songListDetails)) 99 | props.playerStore.handleSongListType("default") 100 | props.playerStore.handleGetSongId(songListDetails[0].id) 101 | props.history.push(`/musicplayer`) 102 | }}> 103 | 104 |

播放全部(共{songListDetails.length}首)

105 |
106 |
107 |
108 | { 109 | songListDetails.map((res, index) => { 110 | return ( 111 |
{ 112 | props.commonStore.getSongListDetails(songListDetails) 113 | sessionStorage.setItem('songListDetails', JSON.stringify(songListDetails)) 114 | props.playerStore.handleSongListType("default") 115 | props.playerStore.handleGetSongId(res.id) 116 | props.history.push(`/musicplayer`) 117 | }}> 118 |
119 | {/* {(index + 1)}. */} 120 | 121 | 122 | 123 |
124 |
125 |
{res.name} {JSON.stringify(res.alia) === '{}' || '[]' ? '' : `(${res.alia[0]})`}
126 |
{res.ar[0].name} - {res.name}
127 |
128 |
129 | 130 |
131 |
132 | ) 133 | }) 134 | } 135 |
136 |
137 | 138 | 139 | } 140 | 141 | export default inject('commonStore', 'playerStore')(observer(PgRecommendedDaily)) -------------------------------------------------------------------------------- /src/views/PgYunCun/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useCallback, useRef } from 'react'; 2 | import { Carousel } from 'antd-mobile'; 3 | import { Icons } from 'components/index' 4 | import { apievent, apisongurl, apihotcomment } from 'api' 5 | import gifMusic from 'assets/images/music.gif' 6 | import './index.scss' 7 | function YunCun(props: any): JSX.Element { 8 | let [cloudVillageReviews, setCloudVillageReviews] = useState>([]) 9 | let [currentSlidingId, setCurrentSlidingId] = useState(1) 10 | let [currentBackground, setCurrentBackground] = useState('') 11 | let [songUrl, setsongUrl] = useState('') 12 | let [hotComments, sethotComments] = useState>([]) 13 | let [autoplay, setautoplay] = useState(false) 14 | let audiosRef = useRef(null) 15 | 16 | const getCloudVillageReviews = useCallback(async (): Promise => { 17 | await apievent().then(res => { 18 | setCloudVillageReviews(res.data) 19 | setCurrentBackground(res.data[0].simpleResourceInfo.songCoverUrl) 20 | getSongUrl(res.data[0].simpleResourceInfo.songId) 21 | }).catch(err => { 22 | }) 23 | }, [cloudVillageReviews]) 24 | 25 | const getSongUrl = useCallback( async (id): Promise => { 26 | if (id){ 27 | let params = { 28 | id: id 29 | } 30 | await apisongurl(params).then(res => { 31 | setsongUrl(res.data[0].url) 32 | }) 33 | await apihotcomment(params).then(res => { 34 | sethotComments(res.hotComments) 35 | setautoplay(true) 36 | }) 37 | audiosRef.current.play() 38 | } 39 | }, []) 40 | 41 | 42 | useEffect((): void => { 43 | getCloudVillageReviews() 44 | }, []) 45 | 46 | const changeCarousel = useCallback((index): void => { 47 | setCurrentSlidingId(index + 1) 48 | audiosRef.current.pause() 49 | }, []) 50 | 51 | useEffect((): void => { 52 | let backgroundResult = cloudVillageReviews.length ? cloudVillageReviews[currentSlidingId - 1].simpleResourceInfo.songCoverUrl : '' 53 | let songUrlId = cloudVillageReviews.length ? cloudVillageReviews[currentSlidingId - 1].simpleResourceInfo.songId : '' 54 | setCurrentBackground(backgroundResult) 55 | getSongUrl(songUrlId) 56 | }, [currentSlidingId]) 57 | 58 | return <> 59 |
60 |
62 |
69 |
70 |
71 |
72 |
73 | { props.history.go(-1) }}> 74 | 75 | 76 |
77 |
78 | 热评墙 79 |
80 |
81 | {currentSlidingId} / {cloudVillageReviews.length} 82 |
83 |
84 |
85 | {}} 90 | afterChange={index => { 91 | changeCarousel(index) 92 | }} 93 | > 94 | { 95 | cloudVillageReviews.length ? cloudVillageReviews.map((item, index) => { 96 | return( 97 |
98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 | {item.simpleUserInfo.nickname} 106 |
107 |
108 |
109 | 110 |
111 |
112 |
113 | {item.content} 114 |
115 |
116 | {item.simpleResourceInfo.name} - {item.simpleResourceInfo.artists[0].name} 117 |
118 | 119 |
120 |
121 |
122 |
123 |
124 | 125 |
126 |
127 |
128 | ) 129 | }) : null 130 | } 131 | 132 |
133 |
134 | 141 | { 142 | hotComments.map(item => ( 143 |
{item.content}
144 | )) 145 | } 146 |
147 |
148 |
149 |
150 | 151 | } 152 | 153 | export default YunCun -------------------------------------------------------------------------------- /src/views/PgYunCun/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .yuncun-wall-wrap-mask { 4 | width: 100%; 5 | height: 100%; 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | filter: blur(20px); 10 | z-index: 0; 11 | box-shadow: inset 0px 0px 200px 5px #555; 12 | } 13 | .audios { 14 | display: none; 15 | } 16 | .yuncun-wall-wrap { 17 | width: 100%; 18 | height: 100%; 19 | position: relative; 20 | top:0; 21 | left:0; 22 | z-index: 2; 23 | display: flex; 24 | flex-direction: column; 25 | .yuncun-wall-header { 26 | width: 100%; 27 | height: px2rem(100); 28 | display: flex; 29 | justify-content: flex-start; 30 | align-items: center; 31 | box-sizing: border-box; 32 | padding: px2rem(20); 33 | // background: #ccc; 34 | color: #fff; 35 | .back-header { 36 | margin-right: px2rem(20); 37 | } 38 | 39 | .text-header { 40 | font-size: px2rem(35); 41 | flex: 1; 42 | } 43 | .speed { 44 | width: px2rem(150); 45 | height: 100%; 46 | text-align: center; 47 | line-height: px2rem(60); 48 | font-size: px2rem(35); 49 | } 50 | .header-icon { 51 | color: #fff; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | } 56 | } 57 | .yuncun-wall-contant { 58 | flex: 1; 59 | position: relative; 60 | // background: red; 61 | .yuncun-wall-space-carousel { 62 | touch-action: none; 63 | width: 100%!important; 64 | height: 100%!important; 65 | .slider-frame { 66 | width: 100% !important; 67 | height: 100% !important; 68 | ul, li{ 69 | width: 100% !important; 70 | height: 100% !important; 71 | } 72 | } 73 | } 74 | .yuncun-wall-contant-tip { 75 | width: 100%; 76 | height: 100%; 77 | // overflow: hidden; 78 | .yuncun-wall-contant-tip-contant { 79 | width: 100%; 80 | // height: px2rem(800); 81 | height: 100%; 82 | margin-top: px2rem(150); 83 | // overflow-y: auto; 84 | // background: #ccc; 85 | padding: px2rem(20); 86 | } 87 | .yuncun-wall-information { 88 | width:100%; 89 | height: 100%; 90 | // background: yellow; 91 | display: flex; 92 | flex-direction: column; 93 | .yuncun-wall-portrait { 94 | width: 100%; 95 | height: px2rem(80); 96 | // background: #fff; 97 | position: relative; 98 | .left-header-image { 99 | width: px2rem(80); 100 | height: px2rem(80); 101 | background: #ccc; 102 | border-radius: 50%; 103 | position: absolute; 104 | z-index: 2; 105 | overflow: hidden; 106 | img{ 107 | width: 100%; 108 | height: 100%; 109 | } 110 | } 111 | .left-header-name { 112 | top: 0; 113 | left: 0; 114 | line-height: px2rem(80); 115 | position: absolute; 116 | padding-left: px2rem(100); 117 | padding-right: px2rem(20); 118 | height: px2rem(80); 119 | border-radius: px2rem(40); 120 | font-size: px2rem(30); 121 | color: #fff; 122 | // background: #ccc; 123 | } 124 | } 125 | .yuncun-wall-punctuation { 126 | width: 100%; 127 | margin-top: px2rem(80); 128 | height: px2rem(100); 129 | // background: blue; 130 | display: flex; 131 | justify-content: flex-start; 132 | align-items: center; 133 | .yuncun-wall-lower-icon { 134 | font-size: px2rem(80); 135 | color: #ccc; 136 | } 137 | } 138 | .yuncun-wall-aspirations { 139 | width: 100%; 140 | flex: 1; 141 | padding: px2rem(20); 142 | .text-alise { 143 | position: relative; 144 | span{ 145 | font-size: px2rem(50); 146 | line-height: px2rem(80); 147 | color: #fff; 148 | font-weight: 800; 149 | } 150 | .yuncun-wall-aspirations-music { 151 | width: 100%; 152 | height: px2rem(160); 153 | padding-top: px2rem(80); 154 | // display: flex; 155 | // justify-content: flex-start; 156 | // align-items: center; 157 | // background: #ccc; 158 | .yuncun-wall-aspirations-content { 159 | width: 100%; 160 | height: 100%; 161 | // background: yellow; 162 | line-height: px2rem(80); 163 | color: #ccc; 164 | font-size: px2rem(30); 165 | position: relative; 166 | } 167 | .git-music { 168 | display: inline-block; 169 | position: absolute; 170 | top: px2rem(-50); 171 | left: 0; 172 | width: px2rem(300); 173 | height: 100%; 174 | // background: red; 175 | // background: url('../../assets/images/music.gif') no-repeat; 176 | img{ 177 | width: 100%; 178 | height: 100%; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | .floating-comment { 187 | position: absolute; 188 | width: 90%; 189 | height: px2rem(90); 190 | top: px2rem(20); 191 | line-height: px2rem(70); 192 | left: px2rem(30); 193 | .my-carousel { 194 | width: 100%; 195 | height: px2rem(90)!important; 196 | .slider-frame { 197 | height: px2rem(90) !important; 198 | ul, li{ 199 | height: px2rem(90) !important; 200 | } 201 | } 202 | } 203 | .yuncun-wall-commit { 204 | width: 100%; 205 | height: 100%; 206 | overflow: hidden; 207 | padding: px2rem(10); 208 | color: #ecf0f1; 209 | overflow: hidden; 210 | white-space: nowrap; 211 | text-overflow: ellipsis; 212 | } 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /src/views/PgVidio/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState, useRef } from 'react' 2 | import { PullToRefresh } from 'antd-mobile'; 3 | import { Skeleton, Icons } from 'components/index' 4 | import './index.scss' 5 | import { apivideogroup, apigrouplist, apivideourl } from 'api' 6 | import { observer, inject } from 'mobx-react' 7 | import { Player, BigPlayButton, ControlBar, ReplayControl } from 'video-react'; 8 | 9 | const tarList: Array = [ 10 | { 11 | tag: '翻跳', 12 | id: 60105 13 | }, 14 | { 15 | tag: '中文翻唱', 16 | id: 57111 17 | }, 18 | { 19 | tag: '听BGM', 20 | id: 58101 21 | }, 22 | { 23 | tag: '动漫混剪', 24 | id: 59115 25 | } 26 | ] 27 | 28 | 29 | function PgVidio(props :any): JSX.Element { 30 | 31 | let [tagId, setTagId] = useState(0) 32 | let [videoList, setVideoList] = useState>([]) 33 | let [listHeight, setListHeight] = useState(0) 34 | let [refreshing, setrefreshing] = useState(true) 35 | let [currentIndex, setCurrentIndex] = useState(null) 36 | 37 | let listRef = useRef(null) 38 | let tagRef = useRef(null) 39 | let videoRef = useRef(null) 40 | 41 | const startPlayVideo = useCallback( async (index): Promise => { 42 | setCurrentIndex(index) 43 | setTimeout(() => { 44 | videoRef.current.play() 45 | }) 46 | }, []) 47 | 48 | const getVideoList = useCallback(async(id): Promise => { 49 | let params = { 50 | id: id 51 | } 52 | 53 | let { datas } = await apigrouplist(params); 54 | let promiseVideos = datas.map(item => { 55 | return apivideourl({id: item.data.vid}) 56 | }) 57 | let result: any[] = await Promise.all(promiseVideos); 58 | 59 | console.log(result) 60 | let videoList = datas.map((item, index) => { 61 | return item = { 62 | ...item, 63 | url: result[index].urls[0].url 64 | } 65 | }) 66 | console.log(videoList) 67 | setVideoList(videoList) 68 | 69 | }, []) 70 | 71 | useEffect((): void => { 72 | getVideoList(tarList[0]['id']) 73 | apivideogroup() 74 | const hei = document.documentElement.clientHeight - tagRef.current.offsetHeight - props.commonStore.tabBarHeight; // 获取到当前可适高度 75 | setListHeight(hei) 76 | }, []) 77 | 78 | const changeTagId = useCallback((index: T): void => { 79 | setCurrentIndex(null) 80 | setTagId(index) 81 | getVideoList(tarList[index]['id']) 82 | }, []) 83 | 84 | return ( 85 | <> 86 |
87 |
88 | { 89 | tarList.map((item, index) => { 90 | return( 91 |
{ 92 | changeTagId(index) 93 | }}> 94 | {item['tag']} 95 |
96 |
97 | ) 98 | }) 99 | } 100 |
101 |
102 | undefined} 114 | onRefresh={ async () => { 115 | setrefreshing(true) 116 | await getVideoList(tarList[tagId]['id']) 117 | setrefreshing(false) 118 | }} 119 | > 120 | { 121 | videoList.length ? videoList.map((item, index) => { 122 | return ( 123 |
124 | 125 |
126 |
{ 127 | startPlayVideo(index) 128 | }}> 129 | {currentIndex == index ? null : } 130 | 131 |
132 | { 133 | currentIndex == index ? 134 | 135 |
136 | 144 | 145 | 146 | 147 | 148 | 149 |
150 | 151 | : 152 | 153 | 154 | } 155 | 156 |
157 |
158 | {item.data.name || item.data.title} 159 |
160 |
161 |
162 | 163 |
164 |
165 | {item.data.artists ? item.data.artists[0].name : '未知'} 166 |
167 |
168 |
169 | ) 170 | }) : 171 | 172 | 173 | } 174 |
175 |
176 |
177 | 178 | ) 179 | } 180 | export default inject('commonStore')(observer(PgVidio)) -------------------------------------------------------------------------------- /src/views/PgSearch/index.scss: -------------------------------------------------------------------------------- 1 | @import 'src/assets/style/px2rem.scss'; 2 | 3 | .search-wrap { 4 | width: 100%; 5 | height: 100%; 6 | // background: #ccc; 7 | display: flex; 8 | flex-direction: column; 9 | overflow: hidden; 10 | 11 | .search-title { 12 | width: 100%; 13 | height: px2rem(100); 14 | display: flex; 15 | justify-content: space-between; 16 | align-items: center; 17 | // background: #ccc; 18 | padding: 0 px2rem(20); 19 | 20 | .search-input { 21 | flex: 1; 22 | height: 100%; 23 | border: 0; 24 | border-bottom: px2rem(1) solid #ccc; 25 | margin: 0 px2rem(30); 26 | } 27 | } 28 | 29 | .search-content { 30 | width: 100%; 31 | flex: 1; 32 | // background: #ccc; 33 | display: flex; 34 | flex-direction: column; 35 | overflow: hidden; 36 | 37 | .search-header-title { 38 | width: 100%; 39 | padding: px2rem(40) px2rem(30); 40 | font-weight: 800; 41 | } 42 | 43 | .search-content-list { 44 | width: 100%; 45 | flex: 1; 46 | overflow-y: auto; 47 | 48 | .search-content-tip { 49 | width: 100%; 50 | height: px2rem(130); 51 | // background: #ccc; 52 | padding: 0 px2rem(0); 53 | display: flex; 54 | justify-content: flex-start; 55 | align-items: center; 56 | 57 | .search-content-tip-number { 58 | width: px2rem(130); 59 | height: px2rem(130); 60 | text-align: center; 61 | line-height: px2rem(130); 62 | } 63 | 64 | .search-content-tip-content { 65 | flex: 1; 66 | height: 100%; 67 | display: flex; 68 | flex-direction: column; 69 | justify-content: center; 70 | 71 | // align-items: center; 72 | .search-content-song-name { 73 | font-weight: 800; 74 | width: px2rem(600); 75 | overflow: hidden; 76 | text-overflow: ellipsis; 77 | white-space: nowrap; 78 | 79 | .introduce { 80 | font-size: px2rem(30); 81 | color: #ccc; 82 | } 83 | } 84 | 85 | .search-content-song-introduce { 86 | color: #ccc; 87 | font-size: px2rem(30); 88 | padding-top: px2rem(20); 89 | width: px2rem(600); 90 | overflow: hidden; 91 | text-overflow: ellipsis; 92 | white-space: nowrap; 93 | 94 | .search-content-song-author { 95 | margin-right: px2rem(20); 96 | width: px2rem(600); 97 | overflow: hidden; 98 | text-overflow: ellipsis; 99 | white-space: nowrap; 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | .album-search-warp { 108 | width: 100%; 109 | 110 | .album-search-tip { 111 | width: 100%; 112 | height: px2rem(150); 113 | // background: yellow; 114 | display: flex; 115 | justify-content: flex-start; 116 | align-items: center; 117 | padding-left: px2rem(15); 118 | 119 | .album-search-pic { 120 | width: px2rem(100); 121 | height: px2rem(100); 122 | background: white; 123 | border-radius: px2rem(15); 124 | overflow: hidden; 125 | 126 | img { 127 | width: 100%; 128 | height: 100%; 129 | } 130 | } 131 | 132 | .album-search-context { 133 | flex: 1; 134 | height: 100%; 135 | display: flex; 136 | flex-direction: column; 137 | justify-content: center; 138 | padding-left: px2rem(20); 139 | 140 | .album-search-context-name { 141 | width: px2rem(600); 142 | overflow: hidden; 143 | text-overflow: ellipsis; 144 | white-space: nowrap; 145 | padding-bottom: px2rem(10); 146 | } 147 | 148 | .album-search-context-author { 149 | font-size: px2rem(25); 150 | color: #ccc; 151 | width: 100%; 152 | } 153 | } 154 | } 155 | } 156 | 157 | .songsheet-wrap { 158 | width: 100%; 159 | 160 | .songsheet-search-tip { 161 | width: 100%; 162 | height: px2rem(150); 163 | display: flex; 164 | justify-content: flex-start; 165 | align-items: center; 166 | padding-left: px2rem(15); 167 | 168 | .songsheet-search-pic { 169 | width: px2rem(100); 170 | height: px2rem(100); 171 | background: white; 172 | border-radius: px2rem(15); 173 | overflow: hidden; 174 | 175 | img { 176 | width: 100%; 177 | height: 100%; 178 | } 179 | } 180 | 181 | .songsheet-search-context { 182 | flex: 1; 183 | height: 100%; 184 | display: flex; 185 | flex-direction: column; 186 | justify-content: center; 187 | padding-left: px2rem(20); 188 | 189 | .songsheet-search-context-name { 190 | width: px2rem(600); 191 | overflow: hidden; 192 | text-overflow: ellipsis; 193 | white-space: nowrap; 194 | padding-bottom: px2rem(10); 195 | } 196 | 197 | .songsheet-search-context-author { 198 | font-size: px2rem(25); 199 | color: #ccc; 200 | width: 100%; 201 | } 202 | } 203 | } 204 | } 205 | 206 | .videos-wrap { 207 | width: 100%; 208 | 209 | .videos-search-tip { 210 | width: 100%; 211 | height: px2rem(200); 212 | display: flex; 213 | justify-content: flex-start; 214 | align-items: center; 215 | padding-left: px2rem(15); 216 | 217 | .videos-search-pic { 218 | width: px2rem(320); 219 | height: px2rem(150); 220 | background: white; 221 | border-radius: px2rem(15); 222 | overflow: hidden; 223 | 224 | img { 225 | width: 100%; 226 | height: 100%; 227 | } 228 | } 229 | 230 | .videos-search-context { 231 | flex: 1; 232 | height: 100%; 233 | display: flex; 234 | flex-direction: column; 235 | justify-content: center; 236 | padding-left: px2rem(20); 237 | 238 | .videos-search-context-name { 239 | width: px2rem(600); 240 | overflow: hidden; 241 | text-overflow: ellipsis; 242 | white-space: nowrap; 243 | padding-bottom: px2rem(30); 244 | } 245 | 246 | .videos-search-context-author { 247 | font-size: px2rem(25); 248 | color: #ccc; 249 | width: 100%; 250 | } 251 | } 252 | } 253 | } 254 | } -------------------------------------------------------------------------------- /src/views/PgLeftSlider/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { List, Badge } from 'antd-mobile' 3 | import { Icons } from '../../components' 4 | import './index.scss' 5 | function PgLeftSlider(props: any): JSX.Element { 6 | 7 | let useMsg = sessionStorage.getItem('useMsg') 8 | 9 | let useMsgs = JSON.parse(useMsg) 10 | 11 | 12 | return 13 |
14 | { 15 | useMsg ? 16 | <> 17 |
18 | 19 |
20 | 21 | {useMsgs.signature} 22 | 23 |

24 | {useMsgs.nickname} 25 | 签到 26 |

27 | 28 | : 29 |
30 |

登陆网易云音乐

31 |

手机电脑多端同步,尽享海量高品质音乐

32 |
{ 33 | props.history.push('/loginphone') 34 | }}> 35 | 立即登陆 36 |
37 |
38 | } 39 | 40 |
41 |
42 |

开通黑胶VIP

43 |

已过期

44 | 45 |
46 |
47 | 披萨免费吃一年 48 |
49 |
50 |
51 |
52 | 53 | 54 | 55 |

我的消息

56 |
57 |
58 | 59 | 60 | 61 |

我的好友

62 |
63 | 64 |
65 | 66 | 67 | 68 |

个性换肤

69 |
70 |
71 | 72 | 73 | 74 |

听歌识曲

75 |
76 |
77 |
78 |
79 |
80 | 81 |

演出

82 |
83 |
84 | 萧金腾 85 |
86 |
87 |
88 |
89 | 90 |

商城

91 |
92 |
93 | 皮卡丘伞39元 94 |
95 |
96 |
97 |
98 | 99 |

附近的人

100 |
101 |
102 | 听说你也在想我 103 |
104 |
105 |
106 |
107 | 108 |

口袋铃声

109 |
110 |
111 | 112 |
113 |
114 |
115 |
116 | 117 |

我的订单

118 |
119 |
120 | 121 |
122 |
123 |
124 |
125 |
126 |
127 | 128 |

我的订单

129 |
130 |
131 | 132 |
133 |
134 |
135 |
136 | 137 |

定时停止播放

138 |
139 |
140 | 141 |
142 |
143 |
144 |
145 | 146 |

扫一扫

147 |
148 |
149 | 150 |
151 |
152 |
153 |
154 | 155 |

音乐闹钟

156 |
157 |
158 | 159 |
160 |
161 |
162 |
163 | 164 |

在线听歌免流量

165 |
166 |
167 | 168 |
169 |
170 |
171 |
172 | 173 |

优惠券

174 |
175 |
176 | 177 |
178 |
179 |
180 |
181 | 182 |

青少年模式

183 |
184 |
185 | 未开启 186 |
187 |
188 | 189 |
190 | 191 | 192 |
193 | 194 |
195 | } 196 | 197 | export default PgLeftSlider --------------------------------------------------------------------------------