├── NeteaseCloudPC ├── .env ├── .gitignore ├── README.md ├── config-overrides.js ├── jsconfig.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo.png │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── api │ │ ├── album.js │ │ ├── common.js │ │ ├── disk.js │ │ ├── dj.js │ │ ├── friend.js │ │ ├── home.js │ │ ├── http.js │ │ ├── login.js │ │ ├── radio.js │ │ ├── search.js │ │ ├── singer.js │ │ ├── toplist.js │ │ ├── user.js │ │ └── video.js │ ├── assets │ │ └── style │ │ │ └── style.scss │ ├── components │ │ ├── Album │ │ │ ├── AlbumContent.jsx │ │ │ └── AlbumTemp.jsx │ │ ├── Aside │ │ │ └── UserInfo.jsx │ │ ├── AsyncComponent.jsx │ │ ├── BackTop.jsx │ │ ├── Banner │ │ │ ├── Banner.jsx │ │ │ └── index.scss │ │ ├── BlockTitle.jsx │ │ ├── Cache.jsx │ │ ├── Comment │ │ │ └── Comment.jsx │ │ ├── Common │ │ │ ├── BtnTools.jsx │ │ │ └── NavTitle.jsx │ │ ├── Covers │ │ │ └── CoverItem.jsx │ │ ├── Footer │ │ │ ├── Footer.jsx │ │ │ └── index.scss │ │ ├── Header │ │ │ ├── Header.jsx │ │ │ └── nav.js │ │ ├── Layout.jsx │ │ ├── Login │ │ │ └── LoginForm.jsx │ │ ├── Main.jsx │ │ ├── PlayBar │ │ │ ├── LrcPanel.jsx │ │ │ ├── PlayBar.jsx │ │ │ └── index.scss │ │ └── VideoPlayer │ │ │ ├── VideoPlayer.jsx │ │ │ └── index.scss │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ ├── router │ │ ├── generateRoute.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── friend.js │ │ │ ├── home.js │ │ │ ├── my.js │ │ │ ├── search.js │ │ │ └── singer.js │ │ └── routerTable.js │ ├── setupTests.js │ ├── store │ │ ├── action-type.js │ │ ├── action.js │ │ ├── global-data.js │ │ ├── index.js │ │ └── user.js │ ├── utils │ │ ├── eventBus.js │ │ ├── hooks.js │ │ ├── localStore.js │ │ ├── methods │ │ │ └── login.js │ │ ├── pureFunctions.js │ │ ├── sessionStore.js │ │ └── utils.js │ └── views │ │ ├── Album │ │ ├── Album.jsx │ │ └── PlayList.jsx │ │ ├── Disk │ │ ├── Disk.jsx │ │ └── components │ │ │ ├── DiskItem.jsx │ │ │ └── DiskWrap.jsx │ │ ├── Friend │ │ ├── Friend.jsx │ │ └── components │ │ │ └── Event.jsx │ │ ├── Home │ │ ├── AsideComponents.jsx │ │ ├── Home.jsx │ │ ├── PlayList │ │ │ ├── CateSelector.jsx │ │ │ └── PlayList.jsx │ │ ├── Rank.jsx │ │ ├── Swiper.jsx │ │ ├── config.js │ │ ├── index.scss │ │ └── store │ │ │ ├── action.js │ │ │ └── index.js │ │ ├── My │ │ ├── Dj.jsx │ │ ├── Login.jsx │ │ ├── MV.jsx │ │ ├── MainPage.jsx │ │ ├── Music.jsx │ │ ├── MyArtist.jsx │ │ ├── PlayList.jsx │ │ ├── components │ │ │ └── menu │ │ │ │ ├── Menu.jsx │ │ │ │ └── mock.js │ │ └── store │ │ │ └── index.js │ │ ├── Personal │ │ ├── Personal.jsx │ │ └── index.scss │ │ ├── Radio │ │ ├── Radio.jsx │ │ ├── RadioCate.jsx │ │ ├── RadioDetails.jsx │ │ ├── components │ │ │ ├── Category.jsx │ │ │ ├── Introduce.jsx │ │ │ ├── RadioTable.jsx │ │ │ ├── Recommend.jsx │ │ │ └── TypeRecommend.jsx │ │ └── store │ │ │ ├── index.js │ │ │ └── initialState.js │ │ ├── Search │ │ ├── Search.jsx │ │ └── components.js │ │ ├── Singer │ │ ├── Album.jsx │ │ ├── CateAside.jsx │ │ ├── Introduce.jsx │ │ ├── MV.jsx │ │ ├── Singer.jsx │ │ ├── SingerCates.jsx │ │ ├── SingerItem.jsx │ │ ├── SingerList.jsx │ │ ├── SingerMoreCates.jsx │ │ └── Song.jsx │ │ ├── Song │ │ ├── Song.jsx │ │ └── components.js │ │ ├── Toplist │ │ ├── AsideNav.jsx │ │ ├── Details.jsx │ │ └── TopList.jsx │ │ └── Video │ │ ├── Components.jsx │ │ └── Video.jsx └── yarn.lock └── README.md /NeteaseCloudPC/.env: -------------------------------------------------------------------------------- 1 | FAST_REFRESH=false 2 | GENERATE_SOURCEMAP=false 3 | PORT=8082 -------------------------------------------------------------------------------- /NeteaseCloudPC/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /neteasecloudmusic 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /NeteaseCloudPC/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /NeteaseCloudPC/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { 2 | override, 3 | addWebpackAlias, 4 | addDecoratorsLegacy, 5 | overrideDevServer, 6 | fixBabelImports, 7 | } = require('customize-cra'); 8 | 9 | const path = require('path'); 10 | const paths = require('react-scripts/config/paths') 11 | const CompressionWebpackPlugin = require('compression-webpack-plugin'); 12 | const rewireUglifyjs = require('react-app-rewire-uglifyjs'); 13 | 14 | /** 15 | * 代理配置 16 | */ 17 | const addProxy = () => config => { 18 | return { 19 | ...config, 20 | proxy: { 21 | '/api': { 22 | // 环境设定后期会讲, 这里可以参考.env中的配置 23 | target: 'http://localhost:5000/', 24 | changeOrigin: true, 25 | pathRewrite: { 26 | '^/api': '', 27 | }, 28 | }, 29 | }, 30 | }; 31 | }; 32 | 33 | //配置路径以及压缩 34 | const buildFile = () => config => { 35 | if (config.mode === 'production') { 36 | console.log('evn is production, change build path...'); 37 | 38 | //打包路径需要设置为相同的名字,不然会报错 39 | paths.appBuild = path.join(path.dirname(paths.appBuild), 'neteasecloudmusic') 40 | config.output.path = path.join(path.dirname(config.output.path), 'neteasecloudmusic') 41 | // 添加js打包gzip配置 42 | config.plugins.push( 43 | new CompressionWebpackPlugin({ 44 | test: /\.js$|\.scss$|\.css$/, 45 | threshold: 1024 46 | }) 47 | ) 48 | } 49 | return config; 50 | } 51 | 52 | //生产环境去除console.* functions 53 | const dropConsole = () => { 54 | return config => { 55 | if (config.optimization.minimizer) { 56 | config.optimization.minimizer.forEach(minimizer => { 57 | if (minimizer.constructor.name === 'TerserPlugin') { 58 | minimizer.options.terserOptions.compress.drop_console = true 59 | } 60 | }) 61 | } 62 | return config 63 | } 64 | } 65 | 66 | /* module.exports = override( 67 | // enable legacy decorators babel plugin 68 | addDecoratorsLegacy(), 69 | addWebpackAlias({ 70 | '@': path.resolve(__dirname, 'src') 71 | }) 72 | ) */ 73 | 74 | module.exports = { 75 | webpack: override( 76 | // 使用修饰器 77 | addDecoratorsLegacy(), 78 | fixBabelImports("import", { 79 | libraryName: "antd", 80 | libraryDirectory: "es", 81 | style: true // change importing css to less 82 | }), 83 | // 路径别名 84 | addWebpackAlias({ 85 | '@': path.resolve(__dirname, 'src'), 86 | '@components': path.resolve(__dirname, 'src/components'), 87 | '@views': path.resolve(__dirname, 'src/views'), 88 | '@assets': path.resolve(__dirname, 'src/assets'), 89 | '@store': path.resolve(__dirname, 'src/store'), 90 | '@utils': path.resolve(__dirname, 'src/utils'), 91 | '@api': path.resolve(__dirname, 'src/api'), 92 | }), 93 | buildFile(), 94 | dropConsole(), 95 | rewireUglifyjs 96 | ), 97 | // 开发环境服务器代理, 一般情况下不需要我们自己配 98 | devServer: overrideDevServer(addProxy()), 99 | }; -------------------------------------------------------------------------------- /NeteaseCloudPC/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "baseUrl": "./", 5 | "paths": { 6 | "@/*": ["src/*"], 7 | "@components/*": ["src/components/*"], 8 | "@views/*": ["src/views/*"], 9 | "@assets/*": ["src/assets/*"], 10 | "@store/*": ["src/store/*"], 11 | "@utils/*": ["src/utils/*"], 12 | "@api/*": ["src/api/*"] 13 | } 14 | }, 15 | "exclude": ["node_modules", "dist"] 16 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ttt", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@vant/area-data": "^1.1.1", 10 | "antd": "^4.12.2", 11 | "axios": "^0.21.1", 12 | "crypto": "^1.0.1", 13 | "customize-cra": "^1.0.0", 14 | "fullscreen-react": "^1.0.4", 15 | "http-proxy-middleware": "^1.0.6", 16 | "node-sass": "4.14.0", 17 | "react": "^17.0.1", 18 | "react-app-rewired": "^2.1.8", 19 | "react-dom": "^17.0.1", 20 | "react-lazy-load-image-component": "^1.5.1", 21 | "react-lazyload": "^3.2.0", 22 | "react-redux": "^7.2.2", 23 | "react-router-dom": "^5.2.0", 24 | "react-scripts": "4.0.2", 25 | "react-transition-group": "^4.4.2", 26 | "redux": "^4.0.5", 27 | "redux-thunk": "^2.3.0", 28 | "web-vitals": "^1.0.1" 29 | }, 30 | "scripts": { 31 | "start": "react-app-rewired start", 32 | "build": "react-app-rewired build", 33 | "test": "react-app-rewired test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@babel/plugin-proposal-decorators": "^7.12.13", 56 | "babel-plugin-import": "^1.13.3", 57 | "compression-webpack-plugin": "5.0.1", 58 | "mini-css-extract-plugin": "^2.2.0", 59 | "prop-types": "^15.7.2", 60 | "react-app-rewire-uglifyjs": "^0.1.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /NeteaseCloudPC/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chensidi/react17-project/92fa757612ce81a226bdc0cc3dd4c3f2556c4706/NeteaseCloudPC/public/favicon.ico -------------------------------------------------------------------------------- /NeteaseCloudPC/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 28 | Jacky 仿网易 仅供学习 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /NeteaseCloudPC/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chensidi/react17-project/92fa757612ce81a226bdc0cc3dd4c3f2556c4706/NeteaseCloudPC/public/logo.png -------------------------------------------------------------------------------- /NeteaseCloudPC/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chensidi/react17-project/92fa757612ce81a226bdc0cc3dd4c3f2556c4706/NeteaseCloudPC/public/logo192.png -------------------------------------------------------------------------------- /NeteaseCloudPC/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chensidi/react17-project/92fa757612ce81a226bdc0cc3dd4c3f2556c4706/NeteaseCloudPC/public/logo512.png -------------------------------------------------------------------------------- /NeteaseCloudPC/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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /NeteaseCloudPC/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /NeteaseCloudPC/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /NeteaseCloudPC/src/App.js: -------------------------------------------------------------------------------- 1 | import Router from './router'; 2 | import Layout from '@components/Layout'; 3 | import BackTop from '@/components/BackTop'; 4 | import localStore from '@/utils/localStore'; 5 | import sessionStore from './utils/sessionStore'; 6 | import loginFn from '@/utils/methods/login'; 7 | import { routerRef } from './router/generateRoute'; 8 | import LrcPanel from '@/components/PlayBar/LrcPanel'; 9 | import { enterTips } from '@/utils/pureFunctions'; 10 | 11 | import { useEffect, useState } from 'react'; 12 | import { useSelector, useDispatch } from 'react-redux'; 13 | 14 | export let historyAlpha; 15 | 16 | function App() { 17 | const token = useSelector(state => state.user.token); 18 | const localUser = localStore.get('user'); 19 | const dispatch = useDispatch(); 20 | 21 | useEffect(() => { 22 | enterTips(); 23 | if (token == null) { //未登录 24 | if (localUser?.remember) { //自动登录 25 | const {phone, password} = localUser; 26 | loginFn.login({phone, password}); 27 | } 28 | } 29 | historyAlpha = routerRef.current; 30 | routerRef.current.history.listen((route) => { 31 | console.log(route); 32 | if (route.pathname.startsWith('/video')) { 33 | dispatch({type: 'setShowPlaybar', show: false}); 34 | document.querySelector('.j-flag.pas')?.click(); 35 | } else { 36 | dispatch({type: 'setShowPlaybar', show: true}); 37 | } 38 | }) 39 | }, []) 40 | return ( 41 | 42 | {Router} 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /NeteaseCloudPC/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/album.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getAlbumInfo(id) { //获取专辑内容 5 | try { 6 | const res = await http.get(`/album`, { 7 | id, 8 | }); 9 | return res; 10 | } catch (err) { 11 | return err; 12 | } 13 | }, 14 | async getAlbumCmt(id, limit = 20, offset = 0) { //获取专辑评论 15 | try { 16 | const res = await http.get(`/comment/album`, { 17 | id, 18 | limit, 19 | offset 20 | }); 21 | return res; 22 | } catch (err) { 23 | return err; 24 | } 25 | }, 26 | async getPlayListInfo(id) { //获取歌单内容 27 | try { 28 | const res = await http.get(`/playlist/detail`, { 29 | id, 30 | }); 31 | return { 32 | album: res.playlist, 33 | songs: res.playlist.tracks 34 | }; 35 | } catch (err) { 36 | return err; 37 | } 38 | }, 39 | async getPlayListCmt(id, limit = 20, offset = 0) { //获取歌单评论 40 | try { 41 | const res = await http.get(`/comment/playlist`, { 42 | id, 43 | limit, 44 | offset 45 | }); 46 | return res; 47 | } catch (err) { 48 | return err; 49 | } 50 | }, 51 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/common.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | import { message } from 'antd'; 3 | 4 | export default { 5 | async getSongUrl(id) { //获取音乐url 6 | try { 7 | const res = await http.get(`/song/url`, { 8 | id, 9 | random: Date.now() 10 | }); 11 | return res.data[0].url; 12 | } catch (err) { 13 | return err; 14 | } 15 | }, 16 | async getLyric(id, key = false) { //获取歌词 17 | try { 18 | message.loading({ 19 | content: '获取歌词中...', 20 | key: 'loadlrc', 21 | duration: 0 , 22 | style: { 23 | marginTop: '40vh', 24 | }, 25 | }); 26 | const res = await http.get(`/lyric`, { 27 | id 28 | }) 29 | if (key) { 30 | return res; 31 | } 32 | return res.lrc.lyric 33 | } catch (err) { 34 | return err; 35 | } finally { 36 | message.destroy('loadlrc'); 37 | } 38 | }, 39 | async getSongDetails(ids) { //获取歌曲详情 40 | try { 41 | const res = await http.get(`/song/detail`, { 42 | ids 43 | }) 44 | return res.songs[0] 45 | } catch (err) { 46 | return err; 47 | } 48 | }, 49 | async getCmtOfSong({id, limit = 20, offset = 0} = {}) { //获取歌曲评论 50 | try { 51 | const res = await http.get(`/comment/music`, { 52 | id, 53 | limit, 54 | offset 55 | }) 56 | return res; 57 | } catch (err) { 58 | return err; 59 | } 60 | }, 61 | async getSameList(id) { //获取相似歌单 62 | try { 63 | const res = await http.get(`/simi/playlist`, { 64 | id, 65 | }) 66 | return res.playlists; 67 | } catch (err) { 68 | return err; 69 | } 70 | }, 71 | async getSameSong(id) { //获取相似歌曲 72 | try { 73 | const res = await http.get(`/simi/song`, { 74 | id, 75 | }) 76 | return res.songs; 77 | } catch (err) { 78 | return err; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/disk.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getNewDisk({ 5 | limit = 10, 6 | offset = 0, 7 | area = 'ALL', 8 | type = 'hot', 9 | year = new Date().getFullYear() 10 | } = {}) { //新碟上架 11 | try { 12 | const res = await http.get(`/top/album`, { 13 | limit, 14 | offset, 15 | area, 16 | type, 17 | year 18 | }); 19 | return res.weekData.slice(0, limit); 20 | } catch (err) { 21 | return err; 22 | } 23 | }, 24 | async getNewDiskByCate({ 25 | limit = 35, 26 | offset = 0, 27 | area = 'ALL', 28 | } = {}) { //新碟上架 29 | try { 30 | const res = await http.get(`/album/new`, { 31 | limit, 32 | offset, 33 | area, 34 | }); 35 | return res; 36 | } catch (err) { 37 | return err; 38 | } 39 | }, 40 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/dj.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getHotDjs(limit = 20) { //最热主播 5 | try { 6 | let res = await http.get(`/dj/toplist/popular`, { 7 | limit, 8 | }); 9 | return res.data; 10 | } catch (err) { 11 | return err; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/friend.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getFriendEvent(pagesize = 20, lasttime = -1) { //朋友动态 5 | try { 6 | let res = await http.get(`/event`, { 7 | pagesize, 8 | lasttime 9 | }); 10 | return res; 11 | } catch (err) { 12 | return err; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/home.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export const homeApis = { 4 | async getBanners() { //首页广告 5 | try { 6 | const res = await http.get(`/banner`); 7 | return res.banners; 8 | } catch (err) { 9 | return err; 10 | } 11 | }, 12 | 13 | async getRecommend(params) { //热门推荐 14 | const {limit=10, order="hot"} = params 15 | try { 16 | const res = await http.get(`/top/playlist`, { 17 | limit, 18 | order 19 | }) 20 | return res.playlists; 21 | } catch (err) { 22 | return err; 23 | } 24 | }, 25 | 26 | async getNewDisk(params) { //新碟上架 27 | const { //新碟上架 28 | limit = 50, 29 | offset = 0, 30 | area = 'ALL', //ALL:全部,ZH:华语,EA:欧美,KR:韩国,JP:日本 31 | } = params; 32 | try { 33 | const res = await http.get(`/album/new`, { 34 | limit, 35 | offset, 36 | area, 37 | }) 38 | return res.albums; 39 | } catch (err) { 40 | return err; 41 | } 42 | }, 43 | 44 | async getTopList() { //所有榜单 45 | try { 46 | const res = await http.get(`/toplist`) 47 | return res.list; 48 | } catch (err) { 49 | return err; 50 | } 51 | }, 52 | 53 | async getRank(id) { //榜单 54 | try { 55 | const res = await http.get(`/playlist/detail`, {id}) 56 | return res.playlist; 57 | } catch (err) { 58 | return err; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import store from '@/store'; 4 | import { historyAlpha } from '@/App'; 5 | import localStore from '@/utils/localStore'; 6 | import loginFn from '@/utils/methods/login'; 7 | 8 | const isDev = process.env.NODE_ENV === 'development'; 9 | 10 | class Http { 11 | instance = null; 12 | constructor() { 13 | this.init(); 14 | } 15 | init() { 16 | // 创建 axios 实例 17 | this.instance = axios.create({ 18 | baseURL: isDev ? '/api' : 'http://zhoup.top:7003/', 19 | timeout: 10000, // 请求超时时间 20 | }) 21 | this.instance.defaults.withCredentials = true; 22 | // 拦截器配置--------------------------------------------------------------------------------------------------------------- 23 | this.instance.interceptors.request.use(function (config) { 24 | // 在发送请求之前做些什么 25 | return config 26 | }, function (error) { 27 | return Promise.reject(error) 28 | }) 29 | this.instance.interceptors.response.use(function (res) { 30 | const { data: {code} } = res; 31 | // do something 32 | if (code === 200) { 33 | return res.data; 34 | } else { 35 | return Promise.reject(res.data); 36 | } 37 | }, function (error) { 38 | console.log(error.response); 39 | const { data: {code, msg} } = error.response 40 | switch (code) { 41 | case 301: //未登录或登录token失效 42 | notLoginHandler(); 43 | return Promise.reject(msg); 44 | case 400: 45 | return Promise.reject(false) 46 | default: 47 | return '系统异常' 48 | } 49 | }) 50 | } 51 | get(url, params = {}) { 52 | return this.instance.get(url, { 53 | params 54 | }) 55 | } 56 | post(url, data = {}) { 57 | return this.instance.post(url, { 58 | data 59 | }) 60 | } 61 | } 62 | 63 | function notLoginHandler() { //未登录或登录失效 64 | const userInfo = store.getState().user; 65 | const localUser = localStore.get('user'); 66 | const {phone, password, remember} = localUser; 67 | //未登录 68 | if (userInfo?.token == undefined) { //没有token记录,则退回首页 69 | historyAlpha.history.replace('/') 70 | } else { //token存在,但已过期 71 | loginFn.logout(historyAlpha.history.replace('/')); 72 | //记住密码情况 73 | if (remember) { 74 | loginFn.login({phone, password}) 75 | } 76 | } 77 | } 78 | 79 | export default new Http(); -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/login.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | import { message } from 'antd'; 3 | 4 | export default { 5 | async login(phone, password) { //登录 6 | try { 7 | const res = await http.get('/login/cellphone', { 8 | phone, 9 | password 10 | }) 11 | if (res.code !== 200) { //未登录成功 12 | message.error(res.message); 13 | return false; 14 | } 15 | message.success(`登录成功!欢迎${res.profile.nickname}来到Jacky网易music`); 16 | return res; 17 | } catch (err) { 18 | console.log(err) 19 | const { msg = '账号错误或不存在' } = err; 20 | message.error(msg); 21 | return { 22 | error: true 23 | }; 24 | } 25 | }, 26 | async logout() { //登出 27 | try { 28 | const res = await http.get('/logout'); 29 | if (res.code === 200) { 30 | message.success('已安全退出'); 31 | return true; 32 | } else { 33 | message.warning('退出失败,请重试!'); 34 | return false; 35 | } 36 | } catch (e) { 37 | message.warning('退出失败,请重试!'); 38 | return false; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/radio.js: -------------------------------------------------------------------------------- 1 | import http from "./http"; 2 | 3 | export default { 4 | async getCateLists() { //电台分类列表 5 | try { 6 | const res = await http.get('/dj/catelist') 7 | return res.categories ?? []; 8 | } catch (err) { 9 | console.log(err); 10 | return []; 11 | } 12 | }, 13 | async getRecommend(limit=10) { //电台推荐 14 | try { 15 | const res = await http.get('/dj/recommend', {limit}) 16 | return res.djRadios ?? []; 17 | } catch (err) { 18 | console.log(err); 19 | return []; 20 | } 21 | }, 22 | async getToplists(limit=10) { //电台节目榜 23 | try { 24 | const res = await http.get('/dj/program/toplist', {limit}) 25 | return res.toplist ?? []; 26 | } catch (err) { 27 | console.log(err); 28 | return []; 29 | } 30 | }, 31 | async getRecommendByType(type) { //电台分类推荐 32 | try { 33 | const res = await http.get('/dj/recommend/type', {type}) 34 | return res.djRadios ?? []; 35 | } catch (err) { 36 | console.log(err); 37 | return []; 38 | } 39 | }, 40 | async getHotByType({limit = 30, offset = 0, cateId} = {}) { //电台-类别热门电台 41 | try { 42 | const res = await http.get('/dj/radio/hot', {limit, offset, cateId}) 43 | return res; 44 | } catch (err) { 45 | console.log(err); 46 | return {}; 47 | } 48 | }, 49 | async getDetails(rid) { //电台详情 50 | try { 51 | const res = await http.get('/dj/detail', {rid}) 52 | return res.data; 53 | } catch (err) { 54 | console.log(err); 55 | return {}; 56 | } 57 | }, 58 | async getProgram(rid, {limit=30, offset=0, asc=false} = {}) { //电台节目详情 59 | try { 60 | const res = await http.get('/dj/program', { 61 | rid, 62 | limit, 63 | offset, 64 | asc 65 | }) 66 | return res; 67 | } catch (err) { 68 | console.log(err); 69 | return {}; 70 | } 71 | }, 72 | 73 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/search.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export const searchApi = { 4 | async searchByKw(keywords, type, limit=30, offset=0) { 5 | try { 6 | const res = await http.get(`/cloudsearch`, { 7 | type, 8 | limit, 9 | offset, 10 | keywords 11 | }); 12 | return res.result; 13 | } catch (err) { 14 | return err; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/singer.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getHotSingers(limit=50, offset=0) { //热门歌手 5 | try { 6 | let res = await http.get(`/top/artists`, { 7 | limit, 8 | offset 9 | }); 10 | return res.artists; 11 | } catch (err) { 12 | return err; 13 | } 14 | }, 15 | async getSongs(id, limit=21, offset=0, order='hot') { //歌手歌曲 16 | try { 17 | let res = await http.get(`/artist/songs`, { 18 | limit, 19 | offset, 20 | id, 21 | order 22 | }); 23 | return res; 24 | } catch (err) { 25 | return err; 26 | } 27 | }, 28 | async getDetails(id) { //歌手详情 29 | try { 30 | let res = await http.get(`/artist/detail`, { 31 | id, 32 | }); 33 | return res.data; 34 | } catch (err) { 35 | return err; 36 | } 37 | }, 38 | async getIntr(id) { //歌手介绍 39 | try { 40 | let res = await http.get(`/artist/desc`, { 41 | id, 42 | }); 43 | return res; 44 | } catch (err) { 45 | return err; 46 | } 47 | }, 48 | async getAlbum(id, limit = 12, offset = 0) { //歌手专辑 49 | try { 50 | let res = await http.get(`/artist/album`, { 51 | id, 52 | limit, 53 | offset, 54 | }); 55 | return res.hotAlbums; 56 | } catch (err) { 57 | return err; 58 | } 59 | }, 60 | async getMv(id, limit = 12, offset = 0) { //歌手mv 61 | try { 62 | let res = await http.get(`/artist/mv`, { 63 | id, 64 | limit, 65 | offset, 66 | }); 67 | return res.mvs; 68 | } catch (err) { 69 | return err; 70 | } 71 | }, 72 | async getSimi(id) { //相似歌手 73 | try { 74 | let res = await http.get(`/simi/artist`, { 75 | id, 76 | }); 77 | return res.artists; 78 | } catch (err) { 79 | return err; 80 | } 81 | }, 82 | async getCateList({ 83 | type = -1, 84 | area = -1, 85 | initial = '', 86 | limit = 50, 87 | offset = 0 88 | }) { //歌手分类列表 89 | try { 90 | let res = await http.get(`/artist/list`, { 91 | type, 92 | area, 93 | initial, 94 | limit, 95 | offset 96 | }); 97 | return res.artists; 98 | } catch (err) { 99 | return err; 100 | } 101 | }, 102 | async topSinger(type = 1) { //歌手排行榜 103 | try { 104 | let res = await http.get(`/toplist/artist`, { 105 | type, 106 | }); 107 | return res.list.artists; 108 | } catch (err) { 109 | return err; 110 | } 111 | }, 112 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/toplist.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getTopLists() { 5 | try { 6 | let res = await http.get(`/toplist`); 7 | return res.list; 8 | } catch (err) { 9 | return err; 10 | } 11 | }, 12 | async getTopDetails(id) { 13 | try { 14 | let res = await http.get(`/playlist/detail`, {id}); 15 | return res.playlist; 16 | } catch (err) { 17 | return err; 18 | } 19 | }, 20 | async getCmts(id, limit=20, offset=0) { 21 | try { 22 | let res = await http.get(`/comment/playlist`, {id, limit, offset}); 23 | return res; 24 | } catch (err) { 25 | return err; 26 | } 27 | }, 28 | async getCatlist() { 29 | try { 30 | let res = await http.get(`/playlist/catlist`); 31 | return res; 32 | } catch (err) { 33 | return err; 34 | } 35 | }, 36 | async getCateLists(params = {}) { 37 | const {limit=35, order="hot", offset=0, cat="全部"} = params 38 | try { 39 | const res = await http.get(`/top/playlist`, { 40 | limit, 41 | order, 42 | offset, 43 | cat 44 | }) 45 | return res; 46 | } catch (err) { 47 | return err; 48 | } 49 | }, 50 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/user.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getRecord(uid, type) { 5 | try { 6 | const res = await http.get('/user/record', { 7 | uid, 8 | type 9 | }) 10 | return res.allData || res.weekData; 11 | } catch (e) { 12 | return e; 13 | } 14 | }, 15 | async getLevel() { 16 | try { 17 | const res = await http.get('/user/level') 18 | return res.data; 19 | } catch (e) { 20 | return e; 21 | } 22 | }, 23 | async getPlayList(uid) { 24 | try { 25 | const res = await http.get('/user/playlist', { 26 | uid 27 | }) 28 | return res.playlist; 29 | } catch (e) { 30 | return e; 31 | } 32 | }, 33 | async getSubscribe(uid) { 34 | try { 35 | const res = await http.get('/user/follows', {uid}) 36 | return res.playlist; 37 | } catch (e) { 38 | return e; 39 | } 40 | }, 41 | async getSubscribeSinger(uid) { 42 | try { 43 | const res = await http.get('/artist/sublist', {uid}) 44 | return res.data; 45 | } catch (e) { 46 | return e; 47 | } 48 | }, 49 | async getSubscribeMv() { 50 | try { 51 | const res = await http.get('/mv/sublist'); 52 | return res.data; 53 | } catch (e) { 54 | return e; 55 | } 56 | }, 57 | async getSubscribeDj() { 58 | try { 59 | const res = await http.get('/dj/sublist'); 60 | return res.djRadios; 61 | } catch (e) { 62 | return e; 63 | } 64 | }, 65 | async signIn(type = 1) { 66 | try { 67 | const res = await http.get('/yunbei/sign', {type}); 68 | return res; 69 | } catch (e) { 70 | return e; 71 | } 72 | }, 73 | async signInInfo() { 74 | try { 75 | const res = await http.get('/yunbei/today'); 76 | return res; 77 | } catch (e) { 78 | return e; 79 | } 80 | }, 81 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/api/video.js: -------------------------------------------------------------------------------- 1 | import http from '@/api/http'; 2 | 3 | export default { 4 | async getVideoInfo(id) { //获取视频信息 5 | try { 6 | let res = await http.get(`/video/detail`, { 7 | id, 8 | }); 9 | return res.data; 10 | } catch (err) { 11 | return err; 12 | } 13 | }, 14 | async getVideoUrl(id) { //获取视频地址 15 | try { 16 | const res = await http.get(`/video/url`, { 17 | id, 18 | }); 19 | return res.urls; 20 | } catch (err) { 21 | return err; 22 | } 23 | }, 24 | async getMVInfo(id) { //mv信息 25 | try { 26 | const res = await http.get(`/mv/detail`, { 27 | mvid: id 28 | }) 29 | return res.data; 30 | } catch (err) { 31 | return err; 32 | } 33 | }, 34 | async getMVUrl(id, r=1080) { //获取MV地址 35 | try { 36 | const res = await http.get(`/mv/url`, { 37 | id, 38 | r 39 | }); 40 | return [{url: res.data.url, r: res.data.r}]; 41 | } catch (err) { 42 | return err; 43 | } 44 | }, 45 | async getCmts(id, limit = 20, offset = 0) { //mv评论 46 | try { 47 | const res = await http.get(`/comment/mv`, { 48 | id, 49 | limit, 50 | offset, 51 | }); 52 | return res; 53 | } catch (err) { 54 | return err; 55 | } 56 | }, 57 | async getVdoCmts(id, limit = 20, offset = 0) { //普通视频评论 58 | try { 59 | const res = await http.get(`/comment/video`, { 60 | id, 61 | limit, 62 | offset, 63 | }); 64 | return res; 65 | } catch (err) { 66 | return err; 67 | } 68 | }, 69 | async getRelatedVdo(id) { //相关视频 70 | try { 71 | const res = await http.get(`/related/allvideo`, { 72 | id, 73 | }); 74 | return res.data; 75 | } catch (err) { 76 | return err; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Album/AlbumContent.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState, useEffect } from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | 4 | let cmtsDatas = {}; 5 | 6 | const withAlbumWrap = (Wrap, fns, flag) => { 7 | 8 | return function() { 9 | const {id} = useParams(); 10 | 11 | const [albumInfo, changeInfo] = useState({}); //专辑/歌单介绍信息 12 | const [songs, changeSongs] = useState([]); //专辑/歌单歌曲内容 13 | const [cmtsData, changeCmtsData] = useState({ //评论内容 14 | total: 0, 15 | hotCmts: [], 16 | cmts: [] 17 | }); 18 | 19 | const getAlbumInfo = useCallback(() => { //获取信息 20 | fns.getInfo(id).then(res => { 21 | changeInfo(res.album); 22 | changeSongs(res.songs); 23 | }) 24 | }, []) 25 | 26 | const getAlbumCmts = useCallback((id, limit = 20, offset = 0) => { //获取评论 27 | fns.getCmts(id, limit, offset).then(res => { 28 | let dataSet = offset > 0 ? { 29 | ...cmtsDatas, 30 | cmts: res.comments 31 | } : { 32 | total: res.total, 33 | hotCmts: res.hotComments, 34 | cmts: res.comments 35 | } 36 | cmtsDatas = dataSet; 37 | changeCmtsData(dataSet); 38 | }) 39 | }, []) 40 | 41 | const changeCmts = async (page, pageSize) => { 42 | await getAlbumCmts(id, 20, (page - 1) * pageSize); 43 | } 44 | 45 | const init = useCallback(() => { //初始化信息 46 | getAlbumInfo(id); 47 | getAlbumCmts(id); 48 | }) 49 | 50 | useEffect(() => { 51 | document.body.scrollTop = 0; 52 | init(); 53 | }, []) 54 | 55 | return 62 | } 63 | } 64 | 65 | export default withAlbumWrap; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Aside/UserInfo.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { useSelector } from 'react-redux'; 3 | 4 | export default () => { 5 | const user = useSelector(state => state.user.profile); 6 | 7 | return ( 8 |
9 |
10 | 11 | 12 | 13 |
14 |

15 | 16 | { user.nickname } 17 | 18 |

19 |

20 | 没有你,阴的天,极讨厌 21 |

22 |
23 |
24 |
    25 |
  • 26 | 27 | { user.eventCount } 28 | 动态 29 | 30 |
  • 31 |
  • 32 | 33 | { user.follows } 34 | 关注 35 | 36 |
  • 37 |
  • 38 | 39 | { user.followeds } 40 | 粉丝 41 | 42 |
  • 43 |
44 |
45 | ) 46 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/AsyncComponent.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import Main from './Main' 3 | 4 | /* 5 | 异步加载组件,类似于vue里的 () => import('xxxxx') 6 | */ 7 | const AsyncComponent = (importFn) => { 8 | return class extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | Com: null 13 | } 14 | } 15 | componentDidMount() { 16 | this.setComponents(); 17 | } 18 | setComponents = () => { 19 | importFn().then(Com => { 20 | this.setState({ 21 | Com: Com.default ? Com.default : null 22 | }) 23 | }) 24 | } 25 | render() { 26 | let Com = this.state.Com; 27 | return ( 28 | Com ? ( 29 | 30 | {this.props.children} 31 | 32 | ) :
33 | ) 34 | } 35 | } 36 | } 37 | 38 | export default AsyncComponent; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/BackTop.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export default () => { 4 | function goTop() { 5 | document.body.scrollTo({top: 0, behavior: "smooth"}); 6 | } 7 | const [show, changeShow] = useState(false); 8 | useEffect(() => { 9 | document.body.addEventListener('scroll', () => { 10 | if (document.body.scrollTop > 0){ 11 | changeShow(true); 12 | } else { 13 | changeShow(false); 14 | } 15 | }) 16 | }, []) 17 | return ( 18 | 19 | ) 20 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Banner/Banner.jsx: -------------------------------------------------------------------------------- 1 | import { Carousel } from 'antd'; 2 | import { useState, memo, useCallback, useEffect } from 'react'; 3 | import { useSelector, useStore } from 'react-redux'; 4 | 5 | import './index.scss'; 6 | import { homeApis } from '@/api/home'; 7 | import homeAction from '@/views/Home/store/action'; 8 | 9 | const Banner = (props) => { 10 | const [bannerIdx, setIdx] = useState(0); 11 | function onChange(from, to) { 12 | setIdx(to); 13 | } 14 | const store = useStore(); 15 | const bannersOfStore = useSelector(state => state.homeData.banners) 16 | 17 | const [bannersData, setBannersData] = useState([]); 18 | const _loadBanners = useCallback(async () => { //轮播图 19 | const res = await homeApis.getBanners(); 20 | setBannersData(res); 21 | store.dispatch(homeAction.setBanners(res)); 22 | }, []) 23 | 24 | function getStoreBanner() { //获取store里面的banner 25 | if (bannersOfStore.length) { 26 | setBannersData(bannersOfStore); 27 | return true; 28 | } else { 29 | return false; 30 | } 31 | } 32 | 33 | useEffect(() => { 34 | /* 35 | 1. 先去store里面拿banner数据 36 | 2. 若发现store里的banner为空则发请求获取 37 | 3. 然后存入store 38 | */ 39 | if (getStoreBanner()) return; 40 | _loadBanners(); 41 | }, []) 42 | 43 | return ( 44 |
45 |
46 | 50 | { 51 | bannersData.map(item => ( 52 |
53 | 54 |
55 | )) 56 | } 57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | export default memo(Banner); -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Banner/index.scss: -------------------------------------------------------------------------------- 1 | .banners { 2 | .banner-wrap{ 3 | position: relative; 4 | width: 982px; 5 | // height: 285px; 6 | margin: 0 auto; 7 | } 8 | .banner-item{ 9 | width: 100%; 10 | } 11 | 12 | background-image: url(http://p1.music.126.net/20vOlcFLxpnyXysMlKLFbw==/109951165699299172.jpg?imageView&blur=40x20); 13 | background-size: 6000px; 14 | background-position: center center; 15 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/BlockTitle.jsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const BlockTitle = (props) => { 5 | const { title, subs = [], path='' } = props; 6 | return ( 7 |
8 | { title.txt } 9 |
10 | { 11 | subs.map((item, i) => { 12 | return ( 13 | 14 | 15 | { item.txt } 16 | 17 | {i < subs.length-1 ? | : null} 18 | 19 | ) 20 | }) 21 | } 22 |
23 | 24 | 更多 25 |   26 | 27 |
28 | ) 29 | } 30 | 31 | export default BlockTitle; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Cache.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | /* 5 | 缓存页面,类似vue的keepalive,但仅仅是存储原来的state,如果数据极大慎用! 6 | */ 7 | 8 | const mapStateToProps = (state) => { 9 | return { 10 | keep: state.globalData.keep 11 | } 12 | } 13 | 14 | const mapDispatchToProps = (dispatch) => { 15 | return { 16 | open: (key) => dispatch({type: 'open', keep: key}) 17 | } 18 | } 19 | 20 | const KeepAlive = (Com) => { 21 | let cache = {}; 22 | class Alive extends Component { 23 | componentWillUnmount() { 24 | const state = this.com.state; 25 | cache = this.props.keep.includes(this.props.name) ? {...state} : {}; 26 | } 27 | componentDidMount() { 28 | console.log(this.props) 29 | console.log(this.com); 30 | const state = this.com.state; 31 | this.props.keep.includes(this.props.name) && this.com.setState({...state, ...cache}); 32 | } 33 | componentWillReceiveProps(nextProps) { 34 | console.log(nextProps); 35 | } 36 | render() { 37 | let dom = this.com = com} {...this.props} />; 38 | return ( 39 | dom 40 | ) 41 | } 42 | } 43 | return connect(mapStateToProps, mapDispatchToProps)(Alive) 44 | } 45 | 46 | export default KeepAlive; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Comment/Comment.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { LazyLoadImage } from 'react-lazy-load-image-component'; 3 | import { Pagination, message } from 'antd'; 4 | import { useState, useCallback } from 'react'; 5 | 6 | function timeFormat(timeStamp) { 7 | const time = new Date(timeStamp); 8 | return time.getFullYear() + '年' 9 | + (time.getMonth() + 1) + '月' 10 | + time.getDate() + '日'; 11 | } 12 | 13 | const Comment = (props) => { 14 | const { user, content, time, likedCount, beReplied } = props; 15 | 16 | return ( 17 |
18 |
19 | 20 | 26 | 27 | 28 |
29 |
30 |
31 |
32 | { user.nickname } 33 | :{ content } 34 |
35 |
36 | { 37 | beReplied.length ? 38 |
39 | { beReplied[0].user.nickname } 40 | :{ beReplied[0].content } 41 |
: null 42 | } 43 |
44 |
{ timeFormat(time) }
45 | 46 | 47 | ({ likedCount }) 48 | 49 |
50 |
51 |
52 | ) 53 | } 54 | 55 | export const CommentWrap = (props) => { 56 | const { total, hotCmts, cmts, onChange } = props; 57 | const [curPage, changePage] = useState(1); 58 | 59 | const pageChange = useCallback(async (page, pageSize) => { 60 | changePage(page); 61 | message.loading({ 62 | content: '获取评论中...', 63 | key: 'loadCmt', 64 | duration: 0 , 65 | style: { 66 | marginTop: '40vh', 67 | }, 68 | }); 69 | await onChange(page, pageSize); 70 | message.destroy('loadCmt') 71 | }, []) 72 | return ( 73 |
74 |
75 |
76 |

77 | 78 | 评论 79 | 80 |

81 | 共{ total }条评论 82 |
83 |
84 |
85 | { 86 | hotCmts.length > 0 ? 87 | <> 88 |
89 | 精彩评论 90 |
91 | { 92 | hotCmts.map(cmt => { 93 | return ( 94 | 95 | ) 96 | }) 97 | } 98 | 99 | : null 100 | } 101 |
102 |
103 | { 104 | cmts.length > 0 ? 105 | <> 106 |
107 | 最新评论 108 |
109 | { 110 | cmts.map(cmt => { 111 | return ( 112 | 113 | ) 114 | }) 115 | } 116 | 117 | : null 118 | } 119 |
120 | { 121 | cmts.length > 0 ? 122 |
123 | 131 |
: null 132 | } 133 |
134 |
135 |
136 | ) 137 | } 138 | 139 | export default Comment; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Common/BtnTools.jsx: -------------------------------------------------------------------------------- 1 | const BtnTools = (props) => { 2 | 3 | const { 4 | shareCount = '', 5 | commentCount = 0, 6 | playHandler = () => {}, 7 | addHandler = () => {} 8 | } = props; 9 | 10 | return ( 11 |
12 |
13 |
14 | 15 | 16 | 播放 17 | 18 |
19 |
20 |
21 | 收藏 22 |
23 |
24 | 分享 { shareCount } 25 |
26 |
27 | 下载 28 |
29 |
30 | 评论 { commentCount } 31 |
32 |
33 |
34 | ) 35 | } 36 | 37 | export default BtnTools; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Common/NavTitle.jsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | 5 | export default (props) => { 6 | const { title = '标题', subs = [] } = props; 7 | 8 | return ( 9 |
10 |

{ title }

11 |
12 | { 13 | subs.map((item, i) => { 14 | return ( 15 | 16 | 17 | { item.txt } 18 | 19 | {i < subs.length-1 ? | : null} 20 | 21 | ) 22 | }) 23 | } 24 |
25 |
26 | ) 27 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Covers/CoverItem.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | const CoverItem = (props) => { 4 | return ( 5 |
  • 6 |
    7 | 8 | 9 |
    10 | props.playFn(props.id)} className="icon-play f-fr"> 11 | 12 | {props.playCount} 13 |
    14 |
    15 |

    16 | {props.name} 17 |

    18 | {props.children} 19 |
  • 20 | ) 21 | } 22 | 23 | export default CoverItem; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import { Fragment } from 'react'; 3 | 4 | const links = ['服务条款', '隐私政策', '儿童隐私政策', '版权投诉指引', '意见反馈']; 5 | const copys = [ 6 | { 7 | href: 'https://web-amped.music.163.com/', 8 | name: 'amped' 9 | }, 10 | { 11 | href: 'https://music.163.com/st/userbasic#/auth', 12 | name: 'auth' 13 | }, 14 | { 15 | href: 'https://music.163.com/musician/artist', 16 | name: 'musician' 17 | }, 18 | { 19 | href: 'https://music.163.com/web/reward', 20 | name: 'reward' 21 | }, 22 | { 23 | href: 'https://music.163.com/uservideo#/plan', 24 | name: 'cash' 25 | } 26 | ] 27 | 28 | const CopyItem = (props) => { 29 | const {name, href} = props; 30 | return ( 31 |
  • 32 | 33 | 34 |
  • 35 | ) 36 | } 37 | 38 | const Footer = () => { 39 | return ( 40 |
    41 |
    42 |
    43 |
    44 |

    45 | { 46 | links.map((link, i) => { 47 | return ( 48 | 49 | {link} 50 | | 51 | 52 | ) 53 | }) 54 | } 55 |

    56 |

    57 | 58 | 网易公司版权所有©1997-2021 59 | 60 | 杭州乐读科技有限公司运营: 61 | 浙网文[2018]3506-263号 62 |

    63 |

    64 | 65 | 违法和不良信息举报电话:0571-89853516 66 | 67 | 68 | 举报邮箱: 69 | 70 | ncm5990@163.com 71 |

    72 |

    73 | 粤B2-20090191-18 工业和信息化部备案管理系统网站 74 | 75 | 76 | 浙公网安备 33010902002564号 77 | 78 |

    79 |
    80 |
      81 | { 82 | copys.map(copy => { 83 | return ( 84 | 85 | ) 86 | }) 87 | } 88 |
    89 |
    90 |
    91 |
    92 | ) 93 | } 94 | 95 | export default Footer; -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Footer/index.scss: -------------------------------------------------------------------------------- 1 | .g-ft { 2 | position: relative; 3 | height: 172px; 4 | overflow: hidden; 5 | border-top: 1px solid #d3d3d3; 6 | background: #f2f2f2; 7 | .copy { 8 | float: left; 9 | width: 520px; 10 | padding-top: 15px; 11 | line-height: 24px; 12 | .line { 13 | margin: 0 8px 0 10px; 14 | color: #c2c2c2; 15 | } 16 | .police-logo { 17 | width: 14px; 18 | height: 14px; 19 | background: url(https://s2.music.126.net/style/web2/img/3rd/police.png?5b7d7a95c38c2c96979fe26536c2e31f) no-repeat; 20 | background-size: cover; 21 | display: inline-block; 22 | margin-right: 2px; 23 | vertical-align: -2px; 24 | } 25 | .police-text { 26 | font-size: 12px; 27 | } 28 | } 29 | } 30 | 31 | .m-ft { 32 | width: 980px; 33 | margin: 0 auto; 34 | .enter { 35 | width: 420px; 36 | margin-top: 33px; 37 | .unit { 38 | float: left; 39 | width: 60px; 40 | height: 70px; 41 | margin-left: 30px; 42 | text-align: center; 43 | color: #666; 44 | &:first-child { 45 | margin-left: 0; 46 | } 47 | } 48 | .tt { 49 | display: inline-block; 50 | margin: 5px 5px 0; 51 | width: 52px; 52 | height: 14px; 53 | } 54 | .tt-amped { 55 | background-position: 0 -108px !important; 56 | margin-left: -6px; 57 | width: 72px; 58 | } 59 | .tt-auth { 60 | background-position: -1px -91px !important; 61 | } 62 | .tt-musician { 63 | background-position: 0 0 !important; 64 | } 65 | .tt-reward { 66 | background-position: 0 -54px !important; 67 | } 68 | .tt-cash { 69 | background-position: -1px -72px !important; 70 | } 71 | } 72 | .logonew { 73 | display: block; 74 | float: none; 75 | width: 50px; 76 | height: 45px; 77 | margin: 0 auto; 78 | &+ .tt { 79 | background: url(https://s2.music.126.net/style/web2/img/foot_enter_tt.png?7c878947e6917400f79b0559edccb43e) no-repeat; 80 | background-size: 180px 139px; 81 | } 82 | } 83 | .logo.logonew { 84 | background: url(https://s2.music.126.net/style/web2/img/foot_enter_new.png?c9f97f7ec7246d8055a94bc6dc229483) no-repeat; 85 | background-size: 110px 552px; 86 | } 87 | .logonew.logo-amped { 88 | background-position: -63px -456.5px; 89 | } 90 | .logonew.logo-auth { 91 | background-position: -63px -101px; 92 | } 93 | .logonew.logo-musician { 94 | background-position: 0 0; 95 | } 96 | .logonew.logo-reward { 97 | background-position: -60px -50px; 98 | } 99 | .logonew.logo-cash { 100 | background-position: 0 -101px; 101 | } 102 | } -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Header/nav.js: -------------------------------------------------------------------------------- 1 | export const navs = [ 2 | { 3 | title: '发现音乐', 4 | path: '/home' 5 | }, 6 | { 7 | title: '我的音乐', 8 | path: '/my' 9 | }, 10 | { 11 | title: '朋友', 12 | path: '/friend' 13 | }, 14 | { 15 | title: '商城', 16 | path: '/Mall' 17 | }, 18 | { 19 | title: '音乐人', 20 | path: '/MusicPeople' 21 | }, 22 | { 23 | title: '下载客户端', 24 | path: '/Download' 25 | }, 26 | ]; 27 | 28 | export const subNav = [ 29 | { 30 | path: '', 31 | name: '推荐' 32 | }, 33 | { 34 | path: '/toplist', 35 | name: '排行榜' 36 | }, 37 | { 38 | path: '/playlist', 39 | name: '歌单' 40 | }, 41 | { 42 | path: '/djradio', 43 | name: '主播电台' 44 | }, 45 | { 46 | path: '/singer', 47 | name: '歌手' 48 | }, 49 | { 50 | path: '/disk', 51 | name: '新碟上架' 52 | }, 53 | ] -------------------------------------------------------------------------------- /NeteaseCloudPC/src/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import Header from '@/components/Header/Header'; 2 | import { HashRouter as Router, useHistory, } from 'react-router-dom'; 3 | import Footer from '@/components/Footer/Footer'; 4 | import PlayBar from '@/components/PlayBar/PlayBar'; 5 | 6 | const Layout = (props) => { 7 | return ( 8 | <> 9 | 10 |
    11 | 12 | {props.children} 13 |