├── .gitignore
├── README.md
├── craco.config.js
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.js
├── assets
│ ├── css
│ │ └── base.css
│ └── img
│ │ ├── banner-control-left.png
│ │ ├── banner-control-right.png
│ │ ├── banner_sprite.png
│ │ ├── download.png
│ │ ├── friend_sprite.jpg
│ │ ├── mine_sprite.png
│ │ ├── playbar_sprite.png
│ │ ├── playlist_sprite.png
│ │ ├── playpanel_bg.png
│ │ ├── progress_bar.png
│ │ ├── radio_slide.png
│ │ ├── recommend-top-bg.png
│ │ ├── singer_sprite.png
│ │ ├── sprite_01.png
│ │ ├── sprite_02.png
│ │ ├── sprite_button.png
│ │ ├── sprite_button2.png
│ │ ├── sprite_cover.png
│ │ ├── sprite_footer_01.png
│ │ ├── sprite_footer_02.png
│ │ ├── sprite_icon.png
│ │ ├── sprite_icon2.png
│ │ ├── sprite_icon3.png
│ │ ├── sprite_table.png
│ │ └── wrap-bg.png
├── common
│ └── global-style.js
├── components
│ ├── album-cover
│ │ ├── index.js
│ │ └── style.js
│ ├── app-footer
│ │ ├── index.js
│ │ └── style.js
│ ├── app-header
│ │ ├── index.js
│ │ └── style.js
│ ├── pagination
│ │ ├── index.js
│ │ └── style.js
│ ├── radio-ranking-cover
│ │ ├── index.js
│ │ └── style.js
│ ├── radio-recommend-cover
│ │ ├── index.js
│ │ └── style.js
│ ├── song-operation-bar
│ │ ├── index.js
│ │ └── style.js
│ ├── theme-cover
│ │ ├── index.js
│ │ └── style.js
│ ├── theme-header-normal
│ │ ├── index.js
│ │ └── style.js
│ ├── theme-header-player
│ │ ├── index.js
│ │ └── style.js
│ ├── theme-header-rcm
│ │ ├── index.js
│ │ └── style.js
│ ├── theme-header-small
│ │ ├── index.js
│ │ └── style.js
│ ├── theme-header-song
│ │ ├── index.js
│ │ └── style.js
│ └── top-ranking
│ │ ├── index.js
│ │ └── style.js
├── index.js
├── pages
│ ├── discover
│ │ ├── c-pages
│ │ │ ├── album
│ │ │ │ ├── c-cpns
│ │ │ │ │ ├── hot-album
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ └── top-album
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ ├── index.js
│ │ │ │ ├── store
│ │ │ │ │ ├── actionCreators.js
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── reducer.js
│ │ │ │ └── style.js
│ │ │ ├── artist
│ │ │ │ ├── c-cpns
│ │ │ │ │ ├── artist-category
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ └── artist-list
│ │ │ │ │ │ ├── c-cpns
│ │ │ │ │ │ ├── alpha-list
│ │ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ │ └── style.js
│ │ │ │ │ │ └── artist-item
│ │ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ │ └── style.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ ├── index.js
│ │ │ │ ├── store
│ │ │ │ │ ├── actionCreators.js
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── reducer.js
│ │ │ │ └── style.js
│ │ │ ├── djradio
│ │ │ │ ├── c-cpns
│ │ │ │ │ ├── radio-category
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── radio-ranking
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ └── radio-recommend
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ ├── index.js
│ │ │ │ ├── store
│ │ │ │ │ ├── actionCreators.js
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── reducer.js
│ │ │ │ └── style.js
│ │ │ ├── ranking
│ │ │ │ ├── c-cpns
│ │ │ │ │ ├── ranking-header
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── ranking-list
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ └── top-ranking
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ ├── index.js
│ │ │ │ ├── store
│ │ │ │ │ ├── actionCreators.js
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── reducer.js
│ │ │ │ └── style.js
│ │ │ ├── recommend
│ │ │ │ ├── c-cpns
│ │ │ │ │ ├── hot-radio
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── hot-recommend
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── new-album
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── ranking-list
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── settle-singer
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ ├── top-banner
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ │ └── user-login
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.js
│ │ │ │ ├── index.js
│ │ │ │ ├── store
│ │ │ │ │ ├── actionCreators.js
│ │ │ │ │ ├── constants.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── reducer.js
│ │ │ │ └── style.js
│ │ │ └── songs
│ │ │ │ ├── c-cpns
│ │ │ │ ├── songs-category
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── style.js
│ │ │ │ ├── songs-header
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── style.js
│ │ │ │ └── songs-list
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── style.js
│ │ │ │ ├── index.js
│ │ │ │ ├── store
│ │ │ │ ├── actionCreators.js
│ │ │ │ ├── constants.js
│ │ │ │ ├── index.js
│ │ │ │ └── reducer.js
│ │ │ │ └── style.js
│ │ ├── index.js
│ │ └── style.js
│ ├── friend
│ │ ├── index.js
│ │ └── style.js
│ ├── main
│ │ └── index.js
│ ├── mine
│ │ ├── index.js
│ │ └── style.js
│ └── player
│ │ ├── app-play-bar
│ │ ├── index.js
│ │ └── style.js
│ │ ├── app-play-panel
│ │ ├── c-cpns
│ │ │ ├── lyric-panel
│ │ │ │ ├── index.js
│ │ │ │ └── style.js
│ │ │ ├── play-header
│ │ │ │ ├── index.js
│ │ │ │ └── style.js
│ │ │ └── play-list
│ │ │ │ ├── index.js
│ │ │ │ └── style.js
│ │ ├── index.js
│ │ └── style.js
│ │ ├── c-cpns
│ │ ├── player-comment
│ │ │ └── index.js
│ │ ├── player-info
│ │ │ ├── index.js
│ │ │ └── style.js
│ │ ├── player-relevant
│ │ │ ├── index.js
│ │ │ └── style.js
│ │ └── player-songs
│ │ │ ├── index.js
│ │ │ └── style.js
│ │ ├── index.js
│ │ ├── store
│ │ ├── actionCreators.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ └── reducer.js
│ │ └── style.js
├── router
│ └── index.js
├── services
│ ├── album.js
│ ├── artist.js
│ ├── axios.js
│ ├── djradio.js
│ ├── local-data.js
│ ├── player.js
│ ├── ranking.js
│ ├── recommend.js
│ └── songs.js
├── store
│ ├── index.js
│ └── reducer.js
└── utils
│ ├── format-utils.js
│ ├── handle-data.js
│ ├── lrc-parse.js
│ └── ui-helper.js
└── yarn.lock
/.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 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React实战之云音乐项目
2 |
3 | > 如果觉得不错,或者对你有帮助,点一个star~ coderwhy
4 |
5 | ### 1.1. 项目简介
6 |
7 | 使用React编写的云音乐PC Web项目,接口来源于开源的接口,自己已经做了部署。
8 |
9 | 项目已经完成功能如下:(你可以下载下来自己体验一下)
10 |
11 | 推荐页面:
12 |
13 | * 轮播图
14 | * 热门推荐
15 | * 新碟上架
16 | * 榜单
17 | * 等等
18 |
19 | 
20 |
21 | 
22 |
23 | 歌曲播放:
24 |
25 | * 目前做了榜单中歌曲的点击播放;
26 | * 事实上其他页面只要将歌曲的id传入到redux中就可以,整个逻辑已经打通;
27 | * 做了歌曲的各种控制(暂停、播放、上一首、下一首、进度改变);
28 | * 做了播放循序切换:顺序播放、随机播放、单曲循环;
29 | * 做了歌词的解析、展示、滚动;
30 |
31 | 
32 |
33 | 排行榜页面:
34 |
35 | * 各种榜单的切换;
36 |
37 | 
38 |
39 | 歌单页面:
40 |
41 | * 选择分类、选择分类后根据分类切换歌单;
42 | * 根据分类,歌单列表的展示;
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 | ### 1.2. 项目规范
72 |
73 | **项目规范:项目中有一些开发规范和代码风格**
74 |
75 | * 1.文件夹、文件名称统一小写、多个单词以连接符(-)连接;
76 | * 2.JavaScript变量名称采用小驼峰标识,常量全部使用大写字母,组件采用大驼峰;
77 | * 3.CSS采用普通CSS和styled-component结合来编写(全局采用普通CSS、局部采用styled-component);
78 | * 4.整个项目不再使用class组件,统一使用函数式组件,并且全面使用Hooks;
79 | * 5.所有的函数式组件,为了避免不必要的渲染,全部使用memo进行包裹;
80 | * 6.组件内部的状态,使用useState、useReducer;业务数据全部放在redux中管理;
81 | * 7.函数组件内部基本按照如下顺序编写代码:
82 | * 组件内部state管理;
83 | * redux的hooks代码;
84 | * 其他组件hooks代码;
85 | * 其他逻辑代码;
86 | * 返回JSX代码;
87 | * 8.redux代码规范如下:
88 | * redux结合ImmutableJS
89 | * 每个模块有自己独立的reducer,通过combineReducer进行合并;
90 | * 异步请求代码使用redux-thunk,并且写在actionCreators中;
91 | * redux直接采用redux hooks方式编写,不再使用connect;
92 | * 9.网络请求采用axios
93 | * 对axios进行二次封装;
94 | * 所有的模块请求会放到一个请求文件中单独管理;
95 | * 10.项目使用AntDesign
96 | * 项目中某些AntDesign中的组件会被拿过来使用;
97 | * 但是多部分组件还是自己进行编写;
98 | * 其他规范在项目中根据实际情况决定和编写;
99 |
100 |
101 |
102 | ### 1.3. 项目运行
103 |
104 | clone项目:
105 |
106 | ```
107 | git clone https://github.com/coderwhy/hy-react-web-music.git
108 | ```
109 |
110 | 安装项目依赖:
111 |
112 | ```shell
113 | yarn install
114 | ```
115 |
116 | 项目运行:
117 |
118 | ```shell
119 | yarn start
120 | ```
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const resolve = dir => path.resolve(__dirname, dir);
4 |
5 | module.exports = {
6 | webpack: {
7 | alias: {
8 | '@': resolve("src"),
9 | 'components': resolve("src/components")
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hy-react-music",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^5.6.4",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.3.2",
9 | "@testing-library/user-event": "^7.1.2",
10 | "antd": "^4.2.4",
11 | "axios": "^0.19.2",
12 | "classnames": "^2.2.6",
13 | "immutable": "^4.0.0-rc.12",
14 | "normalize.css": "^8.0.1",
15 | "react": "^16.13.1",
16 | "react-dom": "^16.13.1",
17 | "react-redux": "^7.2.0",
18 | "react-router-config": "^5.1.1",
19 | "react-router-dom": "^5.2.0",
20 | "react-scripts": "3.4.1",
21 | "redux": "^4.0.5",
22 | "redux-immutable": "^4.0.0",
23 | "redux-thunk": "^2.3.0",
24 | "styled-components": "^5.1.0"
25 | },
26 | "scripts": {
27 | "start": "craco start",
28 | "build": "craco build",
29 | "test": "craco test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": "react-app"
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 | coderwhy music
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Provider } from "react-redux";
4 | import store from "./store";
5 |
6 | import HYMain from './pages/main';
7 |
8 | function App() {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/src/assets/css/base.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 | @import "~normalize.css";
3 |
4 | /* 样式的重置 */
5 | body, html, h1, h2, h3, h4, h5, h6, ul, ol, li, dl, dt, dd, header, menu, section, p, input, td, th, ins {
6 | padding: 0;
7 | margin: 0;
8 | }
9 |
10 | ul, ol, li {
11 | list-style: none;
12 | }
13 |
14 | a {
15 | text-decoration: none;
16 | color: #666;
17 | }
18 |
19 | a:hover {
20 | color: #666;
21 | text-decoration: underline;
22 | }
23 |
24 | i, em {
25 | font-style: normal;
26 | }
27 |
28 | input, textarea, button, select, a {
29 | outline: none;
30 | border: none;
31 | }
32 |
33 | table {
34 | border-collapse: collapse;
35 | border-spacing: 0;
36 | }
37 |
38 | img {
39 | border: none;
40 | vertical-align: middle;
41 | }
42 |
43 | /* 全局样式 */
44 | body, textarea, select, input, button {
45 | font-size: 12px;
46 | color: #333;
47 | font-family: Arial, Helvetica, sans-serif;
48 | background-color: #f5f5f5;
49 | }
50 |
51 | .text-nowrap {
52 | white-space: nowrap;
53 | text-overflow: ellipsis;
54 | overflow: hidden;
55 | }
56 |
57 | .wrap-v1 {
58 | width: 1100px;
59 | margin: 0 auto;
60 | }
61 |
62 | .wrap-v2 {
63 | width: 980px;
64 | margin: 0 auto;
65 | }
66 |
67 | .sprite_01 {
68 | background: url(../img/sprite_01.png) no-repeat 0 9999px;
69 | }
70 |
71 | .sprite_02 {
72 | background: url(../img/sprite_02.png) no-repeat 0 9999px;
73 | }
74 |
75 | .sprite_covor {
76 | background: url(../img/sprite_cover.png) no-repeat 0 9999px;
77 | }
78 |
79 | .sprite_icon {
80 | background: url(../img/sprite_icon.png) no-repeat 0 9999px;
81 | }
82 |
83 | .sprite_icon2 {
84 | background: url(../img/sprite_icon2.png) no-repeat 0 9999px;
85 | }
86 |
87 | .sprite_icon3 {
88 | background: url(../img/sprite_icon3.png) no-repeat 0 9999px;
89 | }
90 |
91 | .sprite_button {
92 | background: url(../img/sprite_button.png) no-repeat 0 9999px;
93 | }
94 |
95 | .sprite_button2 {
96 | background: url(../img/sprite_button2.png) no-repeat 0 9999px;
97 | }
98 |
99 | .sprite_table {
100 | background: url(../img/sprite_table.png) no-repeat 0 9999px;
101 | }
102 |
103 | .sprite_playbar {
104 | background: url(../img/playbar_sprite.png) no-repeat 0 9999px;
105 | }
106 |
107 | .sprite_playlist {
108 | background: url(../img/playlist_sprite.png) no-repeat 0 9999px;
109 | }
110 |
111 | .image_cover {
112 | position: absolute;
113 | left: 0;
114 | right: 0;
115 | top: 0;
116 | bottom: 0;
117 | text-indent: -9999px;
118 | background: url(../img/sprite_cover.png) no-repeat -145px -57px;
119 | }
120 |
121 |
122 | .ant-message .ant-message-notice-content {
123 | position: fixed;
124 | left: 50%;
125 | transform: translateX(-50%);
126 | bottom: 60px;
127 | background-color: rgba(0, 0, 0, .5);
128 | color: #fff;
129 | }
130 |
131 | span:hover.link {
132 | text-decoration: underline;
133 | cursor: pointer;
134 | }
135 |
--------------------------------------------------------------------------------
/src/assets/img/banner-control-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/banner-control-left.png
--------------------------------------------------------------------------------
/src/assets/img/banner-control-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/banner-control-right.png
--------------------------------------------------------------------------------
/src/assets/img/banner_sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/banner_sprite.png
--------------------------------------------------------------------------------
/src/assets/img/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/download.png
--------------------------------------------------------------------------------
/src/assets/img/friend_sprite.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/friend_sprite.jpg
--------------------------------------------------------------------------------
/src/assets/img/mine_sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/mine_sprite.png
--------------------------------------------------------------------------------
/src/assets/img/playbar_sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/playbar_sprite.png
--------------------------------------------------------------------------------
/src/assets/img/playlist_sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/playlist_sprite.png
--------------------------------------------------------------------------------
/src/assets/img/playpanel_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/playpanel_bg.png
--------------------------------------------------------------------------------
/src/assets/img/progress_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/progress_bar.png
--------------------------------------------------------------------------------
/src/assets/img/radio_slide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/radio_slide.png
--------------------------------------------------------------------------------
/src/assets/img/recommend-top-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/recommend-top-bg.png
--------------------------------------------------------------------------------
/src/assets/img/singer_sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/singer_sprite.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_01.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_02.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_button.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_button2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_button2.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_cover.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_footer_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_footer_01.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_footer_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_footer_02.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_icon.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_icon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_icon2.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_icon3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_icon3.png
--------------------------------------------------------------------------------
/src/assets/img/sprite_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/sprite_table.png
--------------------------------------------------------------------------------
/src/assets/img/wrap-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderwhy/hy-react-web-music/0e1c3ad58d1a34277f7b34c4ea4656e3240f2017/src/assets/img/wrap-bg.png
--------------------------------------------------------------------------------
/src/common/global-style.js:
--------------------------------------------------------------------------------
1 | // 扩大可点击区域
2 | const extendClick = () => {
3 | return `
4 | position: relative;
5 | &:before {
6 | content: '';
7 | position: absolute;
8 | top: -10px; bottom: -10px; left: -10px; right: -10px;
9 | };
10 | `
11 | }
12 | // 一行文字溢出部分用... 代替
13 | const noWrap = () => {
14 | return `
15 | text-overflow: ellipsis;
16 | overflow: hidden;
17 | white-space: nowrap;
18 | `
19 | }
20 |
21 | export default {
22 | 'theme-color': '#C20C0C',
23 | 'theme-color-shadow': 'rgba(212, 68, 57, .5)',
24 | 'font-color-light': '#f1f1f1',
25 | 'font-color-desc': '#2E3030',
26 | 'font-color-desc-v2': '#bba8a8',// 略淡
27 | 'font-size-s': '12px',
28 | 'font-size-n': '14px',
29 | 'font-size-l': '16px',
30 | 'font-size-ll': '20px',
31 | "border-color": '#e4e4e4',
32 | 'background-color': '#f2f3f4',
33 | 'background-color-shadow': 'rgba(0, 0, 0, 0.3)',
34 | 'highlight-background-color': '#fff',
35 | extendClick,
36 | noWrap
37 | }
--------------------------------------------------------------------------------
/src/components/album-cover/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import {
4 | getSizeImage
5 | } from "@/utils/format-utils";
6 |
7 | import {
8 | AlbumWrapper
9 | } from "./style";
10 |
11 |
12 | export default memo(function HYAlbumCover(props) {
13 | const { info, size = "100px", width = "118px", bgp = "-570px" } = props;
14 |
15 | return (
16 |
17 |
21 |
22 |
{info.name}
23 |
{info.artist.name}
24 |
25 |
26 | )
27 | })
28 |
--------------------------------------------------------------------------------
/src/components/album-cover/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const AlbumWrapper = styled.div`
4 | .album-image {
5 | position: relative;
6 | width: ${props => props.width};
7 | height: ${props => props.size};
8 | overflow: hidden;
9 | margin-top: 15px;
10 |
11 | img {
12 | width: ${props => props.size};
13 | height: ${props => props.size};
14 | }
15 |
16 | .cover {
17 | position: absolute;
18 | left: 0;
19 | right: 0;
20 | top: 0;
21 | bottom: 0;
22 | background-position: 0 ${props => props.bgp};
23 | text-indent: -9999px;
24 | }
25 | }
26 |
27 | .album-info {
28 | font-size: 12px;
29 | width: ${props => props.size};
30 | .name {
31 | color: #000;
32 | white-space: nowrap;
33 | text-overflow: ellipsis;
34 | overflow: hidden;
35 | }
36 |
37 | .artist {
38 | color: #666;
39 | }
40 | }
41 | `
--------------------------------------------------------------------------------
/src/components/app-footer/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, memo } from 'react';
2 |
3 | import {
4 | footerLinks,
5 | footerImages
6 | } from "@/services/local-data";
7 |
8 | import {
9 | AppFooterWrapper,
10 | FooterLeft,
11 | FooterRight
12 | } from "./style";
13 |
14 | export default memo(function HYAppFooter() {
15 | return (
16 |
17 |
18 |
19 |
20 | {
21 | footerLinks.map(item => {
22 | return (
23 |
24 | {item.title}
25 | |
26 |
27 | )
28 | })
29 | }
30 |
31 |
38 |
39 |
违法和不良信息举报电话:0571-89853516
40 |
41 | 举报邮箱:
42 | ncm5990@163.com
43 |
44 |
45 |
51 |
52 |
53 | {
54 | footerImages.map((item, index) => {
55 | return (
56 |
57 |
58 | {item.title}
59 |
60 | )
61 | })
62 | }
63 |
64 |
65 |
66 | )
67 | })
68 |
--------------------------------------------------------------------------------
/src/components/app-footer/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const AppFooterWrapper = styled.div`
4 | height: 172px;
5 | background-color: #f2f2f2;
6 | color: #666;
7 | border-top: 1px solid #d3d3d3;
8 |
9 | .content {
10 | display: flex;
11 | justify-content: space-between;
12 | align-items: center;
13 | }
14 | `
15 |
16 | export const FooterLeft = styled.div`
17 | padding-top: 15px;
18 | line-height: 24px;
19 |
20 | .link {
21 | a {
22 | color: #999;
23 | }
24 |
25 | .line {
26 | margin: 0 10px;
27 | color: #999;
28 | }
29 | }
30 |
31 | .copyright {
32 | span {
33 | margin-right: 15px;
34 | }
35 | }
36 | `
37 |
38 | export const FooterRight = styled.ul`
39 | display: flex;
40 |
41 | .item {
42 | display: flex;
43 | flex-direction: column;
44 | align-items: center;
45 | margin-right: 40px;
46 |
47 | .link {
48 | display: block;
49 | width: 50px;
50 | height: 45px;
51 |
52 | background-image: url(${require("@/assets/img/sprite_footer_02.png")});
53 | background-size: 110px 450px;
54 | }
55 |
56 | :nth-child(1) .link {
57 | background-position: -60px -101px;
58 | }
59 | :nth-child(2) .link {
60 | background-position: 0 0;
61 | }
62 | :nth-child(2) .link {
63 | background-position: -60px -50px;
64 | }
65 | :nth-child(2) .link {
66 | background-position: 0 -101px;
67 | }
68 |
69 | .title {
70 | margin-top: 5px;
71 | display: block;
72 | width: 52px;
73 | height: 10px;
74 | background-image: url(${require("@/assets/img/sprite_footer_01.png")});
75 | background-size: 180px 100px;
76 | }
77 |
78 | :nth-child(1) .title {
79 | background-position: -1px -90px;
80 | }
81 | :nth-child(2) .title {
82 | background-position: 0 0;
83 | margin-top: 7px;
84 | }
85 | :nth-child(3) .title {
86 | background-position: 0 -54px;
87 | margin-top: 6px;
88 | }
89 |
90 | :nth-child(4) .title {
91 | background-position: -1px -72px;
92 | margin-top: 6px;
93 | }
94 | }
95 | `
96 |
--------------------------------------------------------------------------------
/src/components/app-header/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import classnames from "classnames";
4 |
5 | import { headerLinks } from "@/services/local-data";
6 |
7 | import { Input } from "antd";
8 | import { SearchOutlined } from '@ant-design/icons';
9 |
10 | import {
11 | AppHeaderWrapper,
12 | HeaderLeft,
13 | HeaderRight
14 | } from "./style";
15 |
16 | export default memo(function HYAppHeader() {
17 |
18 | const showItem = (item, index) => {
19 | if (index < 3) {
20 | return (
21 |
22 | {item.title}
23 |
24 |
25 | )
26 | } else {
27 | return {item.title}
28 | }
29 | }
30 |
31 | return (
32 |
33 |
34 |
35 | 网易云音乐
36 |
37 | {
38 | headerLinks.map((item, index) => {
39 | return (
40 |
41 | {showItem(item, index)}
42 |
43 | )
44 | })
45 | }
46 |
47 |
48 |
49 | } />
50 | 创作者中心
51 | 登录
52 |
53 |
54 |
55 |
56 | )
57 | })
58 |
--------------------------------------------------------------------------------
/src/components/app-header/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const AppHeaderWrapper = styled.div`
4 | height: 75px;
5 | background-color: #242424;
6 | font-size: 14px;
7 |
8 | .content {
9 | display: flex;
10 | justify-content: space-between;
11 | }
12 |
13 |
14 | .divider {
15 | height: 5px;
16 | background-color: #C20C0C;
17 | }
18 | `
19 |
20 | export const HeaderLeft = styled.div`
21 | display: flex;
22 |
23 | .logo {
24 | display: block;
25 | width: 176px;
26 | height: 70px;
27 | background-position: 0 0;
28 | text-indent: -9999px;
29 | }
30 |
31 | .select-list {
32 | display: flex;
33 | line-height: 70px;
34 |
35 | .select-item {
36 | position: relative;
37 |
38 | a {
39 | display: block;
40 | padding: 0 20px;
41 | color: #ccc;
42 | }
43 |
44 | :last-of-type a {
45 | position: relative;
46 | ::after {
47 | position: absolute;
48 | content: "";
49 | width: 28px;
50 | height: 19px;
51 | background-image: url(${require("@/assets/img/sprite_01.png")});
52 | background-position: -190px 0;
53 | top: 20px;
54 | right: -15px;
55 | }
56 | }
57 |
58 | &:hover a, .active {
59 | color: #fff;
60 | background: #000;
61 | text-decoration: none;
62 | }
63 |
64 | .active .icon {
65 | position: absolute;
66 | display: inline-block;
67 | width: 12px;
68 | height: 7px;
69 | bottom: -1px;
70 | left: 50%;
71 | transform: translate(-50%, 0);
72 | background-position: -226px 0;
73 | }
74 | }
75 | }
76 | `
77 |
78 | export const HeaderRight = styled.div`
79 | display: flex;
80 | align-items: center;
81 | color: #ccc;
82 | font-size: 12px;
83 |
84 | .search {
85 | width: 158px;
86 | height: 32px;
87 | border-radius: 16px;
88 |
89 | input {
90 | &::placeholder {
91 | font-size: 12px;
92 | }
93 | }
94 | }
95 |
96 | .center {
97 | width: 90px;
98 | height: 32px;
99 | line-height: 32px;
100 | text-align: center;
101 | border: 1px solid #666;
102 | border-radius: 16px;
103 | margin: 0 16px;
104 | }
105 | `
106 |
107 |
108 |
--------------------------------------------------------------------------------
/src/components/pagination/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { Pagination } from 'antd';
4 | import { PaginationWrapper } from './style';
5 |
6 | export default memo(function HYPagination(props) {
7 | const { currentPage, total, onPageChange } = props;
8 |
9 | // render function
10 | function itemRender(current, type, originalElement) {
11 | if (type === 'prev') {
12 | return ;
13 | }
14 | if (type === 'next') {
15 | return ;
16 | }
17 | return originalElement;
18 | }
19 |
20 | return (
21 |
22 |
31 |
32 | )
33 | })
34 |
--------------------------------------------------------------------------------
/src/components/pagination/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PaginationWrapper = styled.div`
4 | .pagination {
5 | margin: 30px 0;
6 | text-align: center;
7 |
8 | .control {
9 | width: 69px;
10 | height: 24px;
11 | line-height: 22px;
12 | text-align: center;
13 | border: 1px solid #ccc;
14 | border-radius: 2px;
15 | margin: 0 5px;
16 | color: #333;
17 |
18 | &:disabled {
19 | color: #999;
20 | }
21 | }
22 |
23 | .ant-pagination-item {
24 | border: 1px solid #ccc !important;
25 | margin: 0 5px;
26 | border-radius: 3px;
27 |
28 | a {
29 | font-size: 12px;
30 | }
31 |
32 | &.ant-pagination-item-active {
33 | background-color: #c20c0c;
34 | border-color: #A2161B;
35 | a {
36 | color: #fff;
37 | }
38 | }
39 | }
40 | }
41 | `
--------------------------------------------------------------------------------
/src/components/radio-ranking-cover/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { getSizeImage } from '@/utils/format-utils'
4 |
5 | import {
6 | CoverWrapper
7 | } from "./style"
8 |
9 | export default memo(function HYRadioRankingCover(props) {
10 | const { radio } = props;
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
{radio.name}
19 |
20 |
21 | {radio.dj.nickname}
22 |
23 |
24 | 共{radio.programCount}期
25 | 订阅{radio.subCount}次
26 |
27 |
28 |
29 | )
30 | })
31 |
--------------------------------------------------------------------------------
/src/components/radio-ranking-cover/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CoverWrapper = styled.div`
4 | display: flex;
5 | align-items: center;
6 | width: 48%;
7 | padding: 20px 0;
8 | border-bottom: 1px solid #e7e7e7;
9 |
10 | img {
11 | width: 120px;
12 | height: 120px;
13 | }
14 |
15 | .info {
16 | margin-left: 20px;
17 |
18 | .nickname {
19 | margin-top: 15px;
20 | color: #000;
21 |
22 | i.left {
23 | display: inline-block;
24 | position: relative;
25 | top: 2px;
26 | width: 14px;
27 | height: 15px;
28 | margin-right: 2px;
29 | background-position: -50px -300px;
30 | }
31 | }
32 |
33 | .count {
34 | color: #666;
35 | margin-top: 5px;
36 | .subscribe {
37 | margin-left: 10px;
38 | }
39 | }
40 | }
41 | `
--------------------------------------------------------------------------------
/src/components/radio-recommend-cover/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { getSizeImage } from "@/utils/format-utils";
4 |
5 | import {
6 | CoverWrapper
7 | } from "./style";
8 |
9 | export default memo(function HYRadioRecommendCover(props) {
10 | const { info } = props;
11 |
12 | return (
13 |
14 |
15 |
16 |
17 | {info.name}
18 | {info.desc}
19 |
20 | )
21 | })
22 |
--------------------------------------------------------------------------------
/src/components/radio-recommend-cover/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CoverWrapper = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | width: 150px;
7 |
8 | .name {
9 | font-size: 14px;
10 | color: #333;
11 | margin: 5px 0;
12 | }
13 |
14 | img {
15 | width: 150px;
16 | height: 150px;
17 | }
18 |
19 | p {
20 | color: #666;
21 | }
22 | `
--------------------------------------------------------------------------------
/src/components/song-operation-bar/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { OperationBarWrapper } from './style';
4 |
5 | export default memo(function HYSongOperationBar(props) {
6 | const { favorTitle, shareTitle, downloadTitle, commentTitle } = props;
7 |
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | 播放
15 |
16 |
17 | +
18 |
19 |
20 | {favorTitle}
21 |
22 |
23 | {shareTitle}
24 |
25 |
26 | {downloadTitle}
27 |
28 |
29 | {commentTitle}
30 |
31 |
32 | )
33 | })
34 |
--------------------------------------------------------------------------------
/src/components/song-operation-bar/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const OperationBarWrapper = styled.div`
4 | display: flex;
5 | align-items: center;
6 |
7 | .play {
8 | display: flex;
9 | align-items: center;
10 | margin-right: 5px;
11 |
12 | .play-icon {
13 | display: inline-block;
14 | height: 31px;
15 | line-height: 31px;
16 | background-position: right -428px;
17 |
18 | .play {
19 | color: #fff;
20 | display: flex;
21 | align-items: center;
22 | padding: 0 7px 0 8px;
23 | background-position: 0 -387px;
24 |
25 | i {
26 | display: inline-block;
27 | width: 20px;
28 | height: 18px;
29 | margin: -2px 2px 0;
30 | background-position: 0 -1622px;
31 | }
32 | }
33 | }
34 |
35 | .add-icon {
36 | display: inline-block;
37 | width: 31px;
38 | height: 31px;
39 | margin-left: -3px;
40 | padding-right: 0;
41 | background-position: 0 -1588px;
42 | text-indent: -9999px;
43 | }
44 | }
45 |
46 | .item {
47 | display: inline-block;
48 | height: 31px;
49 | margin-right: 6px;
50 | padding-right: 5px;
51 | background-position: right -1020px;
52 |
53 | .icon {
54 | display: inline-block;
55 | height: 31px;
56 | line-height: 31px;
57 | padding: 0 7px 0 28px;
58 | font-family: simsun;
59 | }
60 |
61 | .favor-icon {
62 | background-position: 0 -977px;
63 | }
64 |
65 | .share-icon {
66 | background-position: 0 -1225px;
67 | }
68 |
69 | .download-icon {
70 | background-position: 0 -2761px;
71 | }
72 |
73 | .comment-icon {
74 | background-position: 0 -1465px;
75 | }
76 | }
77 | `
--------------------------------------------------------------------------------
/src/components/theme-cover/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import {
4 | getSizeImage,
5 | getCount
6 | } from "@/utils/format-utils";
7 |
8 | import {
9 | ThemeCoverWrapper
10 | } from "./style";
11 |
12 | export default memo(function HYThemeCover(props) {
13 | const { info, right } = props;
14 |
15 | return (
16 |
17 |
18 |

19 |
20 |
21 |
22 |
23 | {getCount(info.playCount)}
24 |
25 |
26 |
27 |
28 |
29 |
30 | {info.name}
31 |
32 | {/*
33 | by {info.copywriter || info.creator.nickname}
34 |
*/}
35 |
36 | )
37 | })
38 |
--------------------------------------------------------------------------------
/src/components/theme-cover/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const ThemeCoverWrapper = styled.div`
4 | width: 140px;
5 | margin: 20px ${props => (props.right || 0)} 20px 0;
6 |
7 | .cover-top {
8 | position: relative;
9 |
10 | &>img {
11 | width: 140px;
12 | height: 140px;
13 | }
14 |
15 | .cover {
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | width: 100%;
20 | height: 100%;
21 | background-position: 0 0;
22 |
23 | .info {
24 | display: flex;
25 | justify-content: space-between;
26 | align-items: center;
27 | padding: 0 10px;
28 | position: absolute;
29 | bottom: 0;
30 | left: 0;
31 | right: 0;
32 | background-position: 0 -537px;
33 | color: #ccc;
34 | height: 27px;
35 |
36 | .erji {
37 | margin-right: 5px;
38 | display: inline-block;
39 | width: 14px;
40 | height: 11px;
41 | background-position: 0 -24px;
42 | }
43 |
44 | .play {
45 | display: inline-block;
46 | width: 16px;
47 | height: 17px;
48 | background-position: 0 0;
49 | }
50 | }
51 | }
52 | }
53 |
54 | .cover-bottom {
55 | font-size: 14px;
56 | color: #000;
57 | margin-top: 5px;
58 | }
59 |
60 | .cover-source {
61 | color: #666;
62 | }
63 | `
--------------------------------------------------------------------------------
/src/components/theme-header-normal/index.js:
--------------------------------------------------------------------------------
1 | import React, {memo} from 'react';
2 |
3 | import {
4 | HeaderWrapper
5 | } from "./style";
6 |
7 | export default memo(function HYThemeHeaderNormal(props) {
8 | const { title, rightSlot } = props;
9 |
10 | return (
11 |
12 | {title}
13 |
14 | {rightSlot}
15 |
16 |
17 | )
18 | })
19 |
--------------------------------------------------------------------------------
/src/components/theme-header-normal/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const HeaderWrapper = styled.div`
4 | display: flex;
5 | padding-bottom: 5px;
6 | border-bottom: 2px solid #c20c0c;
7 | font-family: "Microsoft Yahei", Arial, Helvetica, sans-serif;
8 | font-size: 24px;
9 | `
10 |
--------------------------------------------------------------------------------
/src/components/theme-header-player/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { HeaderWrapper } from './style';
4 |
5 | export default memo(function HYThemeHeaderPlayer(props) {
6 | const { title } = props;
7 |
8 | return (
9 |
10 | {title}
11 |
12 | )
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/theme-header-player/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HeaderWrapper = styled.div`
4 | h3 {
5 | height: 23px;
6 | margin-bottom: 20px;
7 | border-bottom: 1px solid #ccc;
8 | font-size: 12px;
9 | font-weight: 700;
10 | color: #333;
11 | }
12 | `
--------------------------------------------------------------------------------
/src/components/theme-header-rcm/index.js:
--------------------------------------------------------------------------------
1 | import React, {memo} from 'react';
2 | import PropTypes from "prop-types";
3 |
4 | import { Link } from 'react-router-dom';
5 | import {
6 | HeaderWrapper
7 | } from "./style";
8 |
9 | const HYThemeHeaderRCM = memo(function(props) {
10 | const { title, keywords, moreLink, keywordClick } = props;
11 |
12 | return (
13 |
14 |
15 |
{title}
16 |
17 | {
18 | keywords.map((item, index) => {
19 | return (
20 |
21 | keywordClick(item)}>{item}
22 | |
23 |
24 | )
25 | })
26 | }
27 |
28 |
29 |
30 | 更多
31 |
32 |
33 |
34 | )
35 | })
36 |
37 | HYThemeHeaderRCM.defaultProps = {
38 | keywords: []
39 | }
40 |
41 | HYThemeHeaderRCM.propTypes = {
42 | title: PropTypes.string.isRequired,
43 | keywords: PropTypes.array
44 | }
45 |
46 | export default HYThemeHeaderRCM;
47 |
--------------------------------------------------------------------------------
/src/components/theme-header-rcm/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HeaderWrapper = styled.div`
4 | height: 33px;
5 | border-bottom: 2px solid #C10D0C;
6 | padding: 0 10px 4px 34px;
7 | background-position: -225px -156px;
8 |
9 | display: flex;
10 | justify-content: space-between;
11 | align-items: center;
12 |
13 | .left {
14 | display: flex;
15 | align-items: center;
16 |
17 | .title {
18 | font-size: 20px;
19 | font-family: "Microsoft Yahei", Arial, Helvetica, sans-serif;
20 | margin-right: 20px;
21 | }
22 |
23 | .keyword {
24 | display: flex;
25 |
26 | .item {
27 | .divider {
28 | margin: 0 15px;
29 | color: #ccc;
30 | }
31 | }
32 | }
33 | }
34 |
35 | .right {
36 | display: flex;
37 | align-items: center;
38 | .icon {
39 | display: inline-block;
40 | width: 12px;
41 | height: 12px;
42 | margin-left: 4px;
43 | background-position: 0 -240px;
44 | }
45 | }
46 | `
--------------------------------------------------------------------------------
/src/components/theme-header-small/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import {
5 | HeaderWrapper
6 | } from "./style";
7 |
8 | const HYThemeHeaderSmall = memo(function (props) {
9 | const { title, more } = props;
10 |
11 | return (
12 |
13 | {title}
14 | {more}
15 |
16 | )
17 | })
18 |
19 | HYThemeHeaderSmall.defaultProps = {
20 |
21 | }
22 |
23 | HYThemeHeaderSmall.propTypes = {
24 | title: PropTypes.string.isRequired,
25 | more: PropTypes.string
26 | }
27 |
28 | export default HYThemeHeaderSmall;
29 |
--------------------------------------------------------------------------------
/src/components/theme-header-small/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HeaderWrapper = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: flex-end;
7 | padding-bottom: 5px;
8 | border-bottom: 1px solid #ccc;
9 |
10 | h3 {
11 | font-size: 12px;
12 | }
13 | `
14 |
15 |
--------------------------------------------------------------------------------
/src/components/theme-header-song/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector, shallowEqual } from "react-redux";
3 |
4 | import {
5 | HeaderWrapper
6 | } from "./style"
7 | import { memo } from 'react';
8 |
9 | export default memo(function HYThemeHeaderSong() {
10 | // redux
11 | const state = useSelector(state => ({
12 | playList: state.getIn(["ranking", "playList"])
13 | }), shallowEqual)
14 |
15 | return (
16 |
17 |
18 |
歌曲列表
19 |
{state.playList.trackCount}首歌
20 |
21 |
22 | 播放:
23 | {state.playList.playCount}
24 | 次
25 |
26 |
27 | )
28 | })
29 |
--------------------------------------------------------------------------------
/src/components/theme-header-song/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const HeaderWrapper = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | padding-bottom: 5px;
8 | border-bottom: 2px solid #c20c0c;
9 |
10 | .left {
11 | display: flex;
12 | align-items: flex-end;
13 |
14 | .title {
15 | font-size: 20px;
16 | font-family: "Microsoft Yahei", Arial, Helvetica, sans-serif;
17 | }
18 |
19 | .count {
20 | margin-bottom: 5px;
21 | margin-left: 20px;
22 | }
23 | }
24 |
25 | .right {
26 | .count {
27 | color: #c20c0c;
28 | }
29 | }
30 | `
31 |
--------------------------------------------------------------------------------
/src/components/top-ranking/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useDispatch } from 'react-redux';
3 |
4 | import { getSizeImage } from '@/utils/format-utils';
5 | import { getSongDetailAction } from '@/pages/player/store';
6 |
7 | import { TopRankingWrapper } from './style';
8 |
9 | export default memo(function HYTopRanking(props) {
10 | // props and state
11 | const { info } = props;
12 | const { tracks = [] } = info;
13 |
14 | // redux hooks
15 | const dispatch = useDispatch();
16 |
17 | // other handle
18 | const playMusic = (item) => {
19 | dispatch(getSongDetailAction(item.id));
20 | }
21 |
22 | return (
23 |
24 |
37 |
38 | {
39 | tracks.slice(0, 10).map((item, index) => {
40 | return (
41 |
42 |
{index + 1}
43 |
44 |
{item.name}
45 |
46 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | })
55 | }
56 |
57 |
60 |
61 | )
62 | })
63 |
--------------------------------------------------------------------------------
/src/components/top-ranking/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TopRankingWrapper = styled.div`
4 | flex: 1;
5 |
6 | .header {
7 | height: 100px;
8 | display: flex;
9 |
10 | margin: 20px 0 0 20px;
11 |
12 | .image {
13 | width: 80px;
14 | height: 80px;
15 | position: relative;
16 |
17 | img {
18 | width: 80px;
19 | height: 80px;
20 | }
21 | }
22 |
23 | .info {
24 | margin: 5px 0 0 10px;
25 |
26 | a {
27 | font-size: 14px;
28 | color: #333;
29 | font-weight: 700;
30 | }
31 |
32 | .btn {
33 | display: inline-block;
34 | text-indent: -9999px;
35 | width: 22px;
36 | height: 22px;
37 | margin: 8px 10px 0 0;
38 | cursor: pointer;
39 | }
40 |
41 | .play {
42 | background-position: -267px -205px;
43 | }
44 |
45 | .favor {
46 | background-position: -300px -205px;
47 | }
48 | }
49 | }
50 |
51 | .list {
52 | .list-item {
53 | position: relative;
54 | display: flex;
55 | align-items: center;
56 | height: 32px;
57 |
58 | :nth-child(-n+3) .rank {
59 | color: #c10d0c;
60 | }
61 |
62 | .rank {
63 | width: 35px;
64 | text-align: center;
65 | margin-left: 10px;
66 | font-size: 16px;
67 | }
68 |
69 | .info {
70 | color: #000;
71 | width: 170px;
72 | height: 17px;
73 | line-height: 17px;
74 | display: flex;
75 | justify-content: space-between;
76 |
77 | .name {
78 | flex: 1;
79 | }
80 |
81 | .operate {
82 | display: flex;
83 | align-items: center;
84 | display: none;
85 | width: 82px;
86 |
87 | .btn {
88 | width: 17px;
89 | height: 17px;
90 | margin-left: 8px;
91 | cursor: pointer;
92 | }
93 |
94 | .play {
95 | background-position: -267px -268px;
96 | }
97 |
98 | .addto {
99 | position: relative;
100 | top: 2px;
101 | background-position: 0 -700px;
102 | }
103 |
104 | .favor {
105 | background-position: -297px -268px;
106 | }
107 | }
108 | }
109 |
110 |
111 |
112 | &:hover {
113 | .operate {
114 | display: block;
115 | }
116 | }
117 | }
118 | }
119 |
120 | .footer {
121 | height: 32px;
122 | display: flex;
123 | align-items: center;
124 | margin-right: 32px;
125 | justify-content: flex-end;
126 |
127 | a {
128 | color: #000;
129 | }
130 | }
131 | `
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import "./assets/css/base.css";
4 | import App from './App';
5 |
6 |
7 | ReactDOM.render(
8 | ,
9 | document.getElementById('root')
10 | );
11 |
12 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/c-cpns/hot-album/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 |
4 | import { getHotAlbumsAction } from '../../store/actionCreators';
5 |
6 | import HYAlbumCover from '@/components/album-cover';
7 | import HYThemHeaderNormal from '@/components/theme-header-normal';
8 | import {
9 | HotAlbumWrapper
10 | } from './style';
11 |
12 | export default memo(function HYHotAlbum() {
13 |
14 | const { hotAlbums } = useSelector(state => ({
15 | hotAlbums: state.getIn(["album", "hotAlbums"])
16 | }), shallowEqual)
17 | const dispatch = useDispatch();
18 |
19 | useEffect(() => {
20 | dispatch(getHotAlbumsAction());
21 | }, [dispatch]);
22 |
23 | return (
24 |
25 |
26 |
27 | {
28 | hotAlbums.slice(0, 10).map((item, index) => {
29 | return
34 | })
35 | }
36 |
37 |
38 | )
39 | });
40 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/c-cpns/hot-album/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HotAlbumWrapper = styled.div`
4 | .album-list {
5 | display: flex;
6 | flex-wrap: wrap;
7 | justify-content: space-between;
8 | }
9 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/c-cpns/top-album/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect, useState } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 |
4 | import { getTopAlbumsAction } from '../../store/actionCreators';
5 |
6 | import HYThemeHeaderNormal from "@/components/theme-header-normal";
7 | import HYAlbumCover from "@/components/album-cover";
8 | import HYPagination from '@/components/pagination';
9 | import {
10 | TopAlbumWrapper
11 | } from './style';
12 |
13 | export default memo(function HYTopAlbum() {
14 | const [currentPage, setCurrentPage] = useState(1);
15 |
16 | const { topAlbums, total } = useSelector(state => ({
17 | topAlbums: state.getIn(["album", "topAlbums"]),
18 | total: state.getIn(["album", "topTotal"])
19 | }), shallowEqual);
20 | const dispatch = useDispatch();
21 |
22 | useEffect(() => {
23 | dispatch(getTopAlbumsAction(1));
24 | }, [dispatch]);
25 |
26 |
27 | const onPageChange = (page, pageSize) => {
28 | setCurrentPage(page);
29 | dispatch(getTopAlbumsAction(page))
30 | }
31 |
32 | return (
33 |
34 |
35 |
36 | {
37 | topAlbums.map((item, index) => {
38 | return
43 | })
44 | }
45 |
46 |
50 |
51 | )
52 | })
53 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/c-cpns/top-album/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TopAlbumWrapper = styled.div`
4 | margin-top: 30px;
5 | .album-list {
6 | display: flex;
7 | flex-wrap: wrap;
8 | justify-content: space-between;
9 | }
10 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import HYHotAlbum from './c-cpns/hot-album';
4 | import HYTopAlbum from './c-cpns/top-album';
5 | import {
6 | AblumWrapper
7 | } from './style';
8 |
9 | export default memo(function HYAlbum() {
10 | return (
11 |
12 |
13 |
14 |
15 | )
16 | })
17 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './constants';
2 |
3 | import {
4 | getHotAlbums,
5 | getTopAlbums
6 | } from '@/services/album.js';
7 |
8 | const changeHotAlbumsAction = (res) => ({
9 | type: actionTypes.CHANGE_HOT_ALBUMS,
10 | hotAlbums: res.albums
11 | })
12 |
13 | const changeTopAlbumAction = (res) => ({
14 | type: actionTypes.CHANGE_TOP_ALBUMS,
15 | topAlbums: res.albums
16 | })
17 |
18 | const changeTopTotalAction = (total) => ({
19 | type: actionTypes.CHANGE_TOP_TOTAL,
20 | total: total
21 | })
22 |
23 | export const getHotAlbumsAction = () => {
24 | return dispatch => {
25 | getHotAlbums().then(res => {
26 | dispatch(changeHotAlbumsAction(res));
27 | })
28 | }
29 | }
30 |
31 | export const getTopAlbumsAction = (page) => {
32 | return dispatch => {
33 | getTopAlbums(30, (page-1) * 30).then(res => {
34 | dispatch(changeTopAlbumAction(res));
35 | dispatch(changeTopTotalAction(res.total));
36 |
37 | console.log(res)
38 | })
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_HOT_ALBUMS = "ablum/CHANGE_HOT_ALBUMS";
2 | export const CHANGE_TOP_ALBUMS = "album/CHANGE_TOP_ALBUMS";
3 | export const CHANGE_TOP_TOTAL = "album/CHANGE_TOP_TOTAL";
4 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 |
3 | export {
4 | reducer
5 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 |
3 | import * as actionTypes from './constants';
4 |
5 | const defaultState = Map({
6 | hotAlbums: [],
7 | topAlbums: [],
8 | topTotal: 0
9 | })
10 |
11 | export default function reducer(state = defaultState, action) {
12 | switch (action.type) {
13 | case actionTypes.CHANGE_HOT_ALBUMS:
14 | return state.set("hotAlbums", action.hotAlbums);
15 | case actionTypes.CHANGE_TOP_ALBUMS:
16 | return state.set("topAlbums", action.topAlbums);
17 | case actionTypes.CHANGE_TOP_TOTAL:
18 | return state.set("topTotal", action.total);
19 | default:
20 | return state;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/album/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const AblumWrapper = styled.div`
4 | padding: 40px;
5 | background-color: #fff;
6 | border: 1px solid #d3d3d3;
7 | border-width: 0 1px;
8 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-category/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 | import classNames from 'classnames';
4 |
5 | import { artistCategories } from '@/services/local-data';
6 |
7 | import { CategoryWrapper, CategoryItem } from './style';
8 | import { changeCurrentAreaAction, changeCurrentTypeAction } from '../../store/actionCreators';
9 |
10 | export default memo(function HYArtistCategory(props) {
11 |
12 | // redux hooks
13 | const {currentArea, currentType} = useSelector(state => ({
14 | currentArea: state.getIn(["artist", "currentArea"]),
15 | currentType: state.getIn(["artist", "currentType"])
16 | }), shallowEqual);
17 | const dispatch = useDispatch();
18 |
19 | // handle function
20 | const selectArtist = (area, type) => {
21 | dispatch(changeCurrentAreaAction(area));
22 | dispatch(changeCurrentTypeAction(type));
23 | }
24 |
25 | // render jsx
26 | const renderArtist = (artists, area) => {
27 | return (
28 |
29 | {
30 | artists.map((item, index) => {
31 | const isSelect = currentArea === area && currentType.type === item.type;
32 | return (
33 |
35 | selectArtist(area, item)}>{item.name}
36 |
37 | )
38 | })
39 | }
40 |
41 | )
42 | }
43 |
44 | return (
45 |
46 | {
47 | artistCategories.map((item, index) => {
48 | return (
49 |
50 |
{item.title}
51 | {renderArtist(item.artists, item.area)}
52 |
53 | )
54 | })
55 | }
56 |
57 | )
58 | })
59 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-category/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CategoryWrapper = styled.div`
4 | width: 180px;
5 | padding: 50px 10px 40px;
6 | border: 1px solid #d3d3d3;
7 | border-width: 0 1px;
8 |
9 | background-color: #fafafa;
10 |
11 | .section {
12 | border-bottom: 1px solid #d3d3d3;
13 | padding: 10px 0;
14 |
15 | &:last-of-type {
16 | border-bottom: none;
17 | }
18 |
19 | .title {
20 | height: 25px;
21 | padding-left: 14px;
22 | font-size: 16px;
23 | margin-bottom: 5px;
24 | font-family: "Microsoft Yahei";
25 | }
26 | }
27 |
28 |
29 | `
30 |
31 | export const CategoryItem = styled.div`
32 | width: 160px;
33 | color: #333;
34 | height: 29px;
35 | line-height: 29px;
36 | margin-bottom: 2px;
37 | cursor: pointer;
38 |
39 | &.active {
40 | span {
41 | color: red;
42 | background-position: 0 0;
43 | }
44 | }
45 |
46 | span {
47 | display: inline-block;
48 | width: 160px;
49 | padding-left: 27px;
50 | background: url(${require("@/assets/img/singer_sprite.png")}) no-repeat 0 -30px;
51 | cursor: pointer;
52 |
53 | &:hover {
54 | text-decoration: underline;
55 | }
56 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-list/c-cpns/alpha-list/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useState, useEffect } from 'react';
2 | import { useSelector, shallowEqual, useDispatch } from 'react-redux';
3 | import classNames from 'classnames';
4 |
5 | import { singerAlphas } from '@/utils/handle-data';
6 | import { getArtistListAction } from '../../../../store/actionCreators';
7 |
8 | import {
9 | AlphaListWrapper
10 | } from './style';
11 |
12 | export default memo(function HYAlphaList() {
13 | const [currentAlpha, setCurrentAlpha] = useState("-1");
14 |
15 | const { currentType, currentArea } = useSelector(state => ({
16 | currentType: state.getIn(["artist", "currentType"]),
17 | currentArea: state.getIn(["artist", "currentArea"])
18 | }), shallowEqual);
19 | const dispatch = useDispatch();
20 |
21 | useEffect(() => {
22 | setCurrentAlpha("-1");
23 | }, [currentType, currentArea]);
24 | useEffect(() => {
25 | dispatch(getArtistListAction(currentArea, currentType.type, currentAlpha));
26 | }, [currentAlpha, currentType, currentArea, dispatch]);
27 |
28 | return (
29 |
30 | {
31 | currentArea !== -1 && singerAlphas.map((item, index) => {
32 | const isActive = currentAlpha === item;
33 | if (item === "0") item = "其他";
34 | if (item === "-1") item = "热门";
35 | return (
36 |
38 | setCurrentAlpha(item)}>{item.toUpperCase()}
39 |
40 | )
41 | })
42 | }
43 |
44 | )
45 | })
46 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-list/c-cpns/alpha-list/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const AlphaListWrapper = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | margin-top: ${props => props.hasTop ? "20px": 0};
7 |
8 | .item {
9 | padding: 1px 4px;
10 | border-radius: 3px;
11 | span {
12 | font-size: 14px;
13 | color: #333;
14 | cursor: pointer;
15 | }
16 |
17 | span:hover {
18 | text-decoration: underline;
19 | }
20 | }
21 |
22 | .active {
23 | background-color: #c20c0c;
24 | span {
25 | color: #fff;
26 | }
27 | }
28 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-list/c-cpns/artist-item/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { getSizeImage } from '@/utils/format-utils';
4 |
5 | import { ItemWrapper } from './style';
6 |
7 | export default memo(function HYArtistItemV1(props) {
8 | const { info, index } = props;
9 |
10 | return (
11 |
12 | {
13 | index < 10 && (
14 |
15 |

16 |
17 | )
18 | }
19 |
20 | {info.name}
21 |
22 |
23 |
24 | )
25 | })
26 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-list/c-cpns/artist-item/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ItemWrapper = styled.div`
4 | width: 130px;
5 | margin-top: 15px;
6 |
7 | .image {
8 | img {
9 | width: 130px;
10 | height: 130px;
11 | }
12 | }
13 |
14 | .info {
15 | margin: 10px 0;
16 | display: flex;
17 | justify-content: space-between;
18 |
19 | .name {
20 | cursor: pointer;
21 |
22 | &:hover {
23 | color: red;
24 | text-decoration: underline;
25 | }
26 | }
27 |
28 | .icon {
29 | display: inline-block;
30 | width: 17px;
31 | height: 18px;
32 | background-position: 0 -740px;
33 | }
34 | }
35 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-list/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useSelector, shallowEqual } from 'react-redux';
3 |
4 |
5 | import HYThemeHeaderNormal from '@/components/theme-header-normal';
6 | import HYAlphaList from './c-cpns/alpha-list';
7 | import HYArtistItem from './c-cpns/artist-item';
8 | import {
9 | ArtistListWrapper
10 | } from './style';
11 |
12 | export default memo(function HYArtistList() {
13 | // redux hooks
14 | const { currentType, artistList } = useSelector(state => ({
15 | currentType: state.getIn(["artist", "currentType"]),
16 | artistList: state.getIn(["artist", "artistList"])
17 | }), shallowEqual);
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | {
25 | artistList.map((item, index) => {
26 | return
27 | })
28 | }
29 |
30 |
31 | )
32 | })
33 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/c-cpns/artist-list/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ArtistListWrapper = styled.div`
4 | flex: 1;
5 | padding: 40px;
6 |
7 | .artist-list {
8 | display: flex;
9 | flex-wrap: wrap;
10 | justify-content: space-between;
11 |
12 | padding: 5px 0 40px;
13 | }
14 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import HYArtistCategory from './c-cpns/artist-category';
4 | import HYArtistList from './c-cpns/artist-list';
5 | import { HYArtistWrapper } from './style';
6 |
7 | export default memo(function HYArtist() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 | })
17 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './constants';
2 |
3 | import { getArtistList } from '@/services/artist';
4 |
5 | const changeArtistListAction = (artistList) => ({
6 | type: actionTypes.CHANGE_ARTIST_LIST,
7 | artistList
8 | })
9 |
10 | export const changeCurrentAreaAction = (area) => ({
11 | type: actionTypes.CHANGE_CURRENT_AREA,
12 | currentArea: area
13 | });
14 |
15 | export const changeCurrentTypeAction = (type) => ({
16 | type: actionTypes.CHANGE_CURRENT_TYPE,
17 | currentType: type
18 | });
19 |
20 | export const getArtistListAction = (area, type, alpha) => {
21 | return dispatch => {
22 | getArtistList(area, type, alpha).then(res => {
23 | console.log(res);
24 | dispatch(changeArtistListAction(res.artists))
25 | })
26 | }
27 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_CURRENT_AREA = "artist/CHANGE_CURRENT_AREA";
2 | export const CHANGE_CURRENT_TYPE = "artist/CHANGE_CURRENT_TYPE";
3 |
4 | export const CHANGE_ARTIST_LIST = "artist/CHANGE_ARTIST_LIST";
5 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 |
3 | export {
4 | reducer
5 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 |
3 | import * as actionTypes from './constants';
4 |
5 | const defaultState = Map({
6 | currentArea: 7,
7 | currentType: {
8 | name: "推荐歌手",
9 | type: 1
10 | },
11 | artistList: []
12 | });
13 |
14 | function reducer(state = defaultState, action) {
15 | switch(action.type) {
16 | case actionTypes.CHANGE_CURRENT_AREA:
17 | return state.set("currentArea", action.currentArea);
18 | case actionTypes.CHANGE_CURRENT_TYPE:
19 | return state.set("currentType", action.currentType);
20 | case actionTypes.CHANGE_ARTIST_LIST:
21 | return state.set("artistList", action.artistList);
22 | default:
23 | return state;
24 | }
25 | }
26 |
27 | export default reducer;
28 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/artist/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HYArtistWrapper = styled.div`
4 | .content {
5 | display: flex;
6 | background-color: #fff;
7 | }
8 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/c-cpns/radio-category/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, memo } from 'react';
2 | import { useSelector, useDispatch, shallowEqual } from 'react-redux';
3 | import classnames from 'classnames';
4 |
5 | import {
6 | getRadioCategories,
7 | changeCurrentIdActio
8 | } from "../../store/actionCreators";
9 |
10 | import { Carousel } from 'antd';
11 | import {
12 | CategoryWrapper,
13 | CategoryContent,
14 | CategoryItemImage
15 | } from "./style";
16 |
17 | const PAGE_SIZE = 16;
18 |
19 | export default memo(function HYRadioCategory() {
20 | // redux
21 | const dispatch = useDispatch();
22 | const { categories, currentId } = useSelector(state => ({
23 | categories: state.getIn(["djradio", "categories"]),
24 | currentId: state.getIn(["djradio", "currentId"])
25 | }), shallowEqual);
26 |
27 | // data handle
28 | const page = Math.ceil(categories.length / PAGE_SIZE) || 1;
29 |
30 | // hooks
31 | useEffect(() => {
32 | dispatch(getRadioCategories());
33 | }, [dispatch]);
34 | const carouselRef = useRef();
35 |
36 | // handle function
37 | function getSize(index) {
38 | return index * PAGE_SIZE > categories.length ? index * PAGE_SIZE : categories.length;
39 | }
40 |
41 | return (
42 |
43 | carouselRef.current.prev()}>
44 |
45 |
46 | {
47 | Array(page).fill(0).map((_, index) => {
48 | return (
49 |
50 | {
51 | categories.slice(index * PAGE_SIZE, getSize(index + 1)).map((item, indey) => {
52 | return (
53 |
dispatch(changeCurrentIdActio(item.id))}
55 | className={classnames("category-item", {"active": currentId === item.id})}>
56 |
57 | {item.name}
58 |
59 | )
60 | })
61 | }
62 |
63 | )
64 | })
65 | }
66 |
67 |
68 | carouselRef.current.next()}>
69 |
70 | )
71 | })
72 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/c-cpns/radio-category/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CategoryWrapper = styled.div`
4 | display: flex;
5 | align-items: center;
6 | margin: 0 -40px;
7 |
8 | .arrow {
9 | width: 20px;
10 | height: 30px;
11 | background-image: url(${require("@/assets/img/radio_slide.png")});
12 | cursor: pointer;
13 | }
14 |
15 | .arrow-left {
16 | margin-left: 15px;
17 | background-position: 0 -30px;
18 | }
19 |
20 | .arrow-right {
21 | margin-right: 15px;
22 | background-position: -30px -30px;
23 | }
24 | `
25 |
26 | export const CategoryContent = styled.div`
27 | flex: 1;
28 | width: 900px;
29 |
30 | .category-page {
31 | display: flex !important;
32 | flex-wrap: wrap;
33 | padding-bottom: 20px;
34 |
35 | .category-item {
36 | display: flex;
37 | flex-direction: column;
38 | align-items: center;
39 | margin: 10px;
40 | width: 70px;
41 | height: 70px;
42 | font-size: 12px;
43 | cursor: pointer;
44 | border-radius: 5px;
45 | text-align: center;
46 | border: 2px solid transparent;
47 |
48 | :hover {
49 | background-color: #eee;
50 | }
51 |
52 | &.active {
53 | color: #C20C0C;
54 | border: 2px solid #d35757;
55 |
56 | .image {
57 | background-position: -48px 0;
58 | }
59 | }
60 | }
61 | }
62 |
63 | .dots {
64 | bottom: 5px;
65 | li {
66 | width: 20px;
67 | height: 20px;
68 | display: flex;
69 | align-items: center;
70 |
71 | button {
72 | width: 6px;
73 | height: 6px;
74 | border-radius: 50%;
75 | background-color: #aaa;
76 | }
77 | }
78 |
79 | li.slick-active {
80 | width: 20px;
81 | button {
82 | background-color: #C20C0C;
83 | }
84 | }
85 | }
86 | `
87 |
88 | export const CategoryItemImage = styled.div`
89 | width: 48px;
90 | height: 48px;
91 | background-image: url(${props => props.imgUrl});
92 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/c-cpns/radio-ranking/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import { useSelector, useDispatch, shallowEqual } from 'react-redux'
3 |
4 | import {
5 | getRadios
6 | } from "../../store/actionCreators";
7 |
8 | import HYThemeHeaderNormal from '@/components/theme-header-normal';
9 | import HYRadioRankingCover from '@/components/radio-ranking-cover';
10 | import HYPagination from '@/components/pagination';
11 | import {
12 | RankingWraper
13 | } from "./style";
14 | import { useState } from 'react';
15 |
16 | export default memo(function HYRadioRanking() {
17 | // state
18 | const [currentPage, setCurrentPage] = useState(1);
19 |
20 | // redux
21 | const { currentId, radios } = useSelector(state => ({
22 | currentId: state.getIn(["djradio", "currentId"]),
23 | radios: state.getIn(["djradio", "radios"])
24 | }), shallowEqual)
25 | const dispatch = useDispatch();
26 |
27 | // hooks
28 | useEffect(() => {
29 | if (currentId === 0) return;
30 | dispatch(getRadios(currentId, 0))
31 | }, [dispatch, currentId]);
32 |
33 | // hanlde function
34 | const onPageChange = (page, pageSize) => {
35 | setCurrentPage(page);
36 | dispatch(getRadios(currentId, page * 30));
37 | }
38 |
39 | return (
40 |
41 |
42 |
43 | {
44 | radios.map((item, index) => {
45 | return ()
46 | })
47 | }
48 |
49 |
53 |
54 | )
55 | })
56 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/c-cpns/radio-ranking/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RankingWraper = styled.div`
4 | .ranking-list {
5 | display: flex;
6 | justify-content: space-between;
7 | flex-wrap: wrap;
8 | }
9 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/c-cpns/radio-recommend/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import { useSelector, useDispatch, shallowEqual } from 'react-redux';
3 |
4 | import {
5 | getRadioRecommend
6 | } from "../../store/actionCreators";
7 |
8 | import HYThemeHeaderNormal from '@/components/theme-header-normal';
9 | import HYRadioRecomendCover from '@/components/radio-recommend-cover';
10 | import {
11 | RecommendWrapper
12 | } from "./style";
13 |
14 | export default memo(function HYRadioRecommend() {
15 | // redux
16 | const { currentId, recommends } = useSelector(state => ({
17 | currentId: state.getIn(["djradio", "currentId"]),
18 | recommends: state.getIn(["djradio", "recommends"])
19 | }), shallowEqual);
20 | const dispatch = useDispatch();
21 |
22 | // hooks
23 | useEffect(() => {
24 | if (currentId === 0) return;
25 | dispatch(getRadioRecommend(currentId));
26 | }, [dispatch, currentId])
27 |
28 | return (
29 |
30 |
31 |
32 | {
33 | recommends.slice(0, 5).map((item) => {
34 | return ()
35 | })
36 | }
37 |
38 |
39 | )
40 | })
41 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/c-cpns/radio-recommend/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RecommendWrapper = styled.div`
4 | .radio-list {
5 | margin: 20px 0 40px;
6 | display: flex;
7 | justify-content: space-between;
8 | }
9 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import HYRadioCategory from './c-cpns/radio-category';
4 | import HYRadioRecommend from './c-cpns/radio-recommend';
5 | import HYRadioRanking from './c-cpns/radio-ranking';
6 | import {
7 | DjRadioWrapper
8 | } from "./style";
9 |
10 | export default memo(function HYDjradio() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | )
18 | })
19 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import {
2 | getDjRadioCatelist,
3 | getDjRadioRecommend,
4 | getDjRadios
5 | } from "@/services/djradio";
6 | import * as actionTypes from './constants';
7 |
8 | const changeCategoryAction = (res) => ({
9 | type: actionTypes.CHANGE_RADIO_CATEGORY,
10 | categories: res.categories
11 | })
12 |
13 | const changeRecommendsAction = (res) => ({
14 | type: actionTypes.CHANGE_RECOMMENDS,
15 | recommends: res.djRadios
16 | })
17 |
18 | const changeRadiosAction = (res) => ({
19 | type: actionTypes.CHANGE_RADIOS,
20 | radios: res.djRadios
21 | })
22 |
23 | export const changeCurrentIdActio = (id) => ({
24 | type: actionTypes.CHANGE_CURRENT_ID,
25 | currentId: id
26 | })
27 |
28 | export const getRadioCategories = () => {
29 | return dispatch => {
30 | getDjRadioCatelist().then(res => {
31 | dispatch(changeCategoryAction(res));
32 | const currentId = res.categories[0].id;
33 | dispatch(changeCurrentIdActio(currentId));
34 | })
35 | }
36 | }
37 |
38 | export const getRadioRecommend = (currentId) => {
39 | return dispatch => {
40 | getDjRadioRecommend(currentId).then(res => {
41 | dispatch(changeRecommendsAction(res));
42 | })
43 | }
44 | }
45 |
46 | export const getRadios = (currentId, offset) => {
47 | return dispatch => {
48 | getDjRadios(currentId, 30, offset).then(res => {
49 | dispatch(changeRadiosAction(res));
50 | })
51 | }
52 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_RADIO_CATEGORY = "djradio/CHANGE_RADIO_CATEGORY";
2 | export const CHANGE_CURRENT_ID = "djradio/CHANGE_CURRENT_ID";
3 | export const CHANGE_RECOMMENDS = "djradio/CHANGE_RECOMMENDS";
4 | export const CHANGE_RADIOS = "djradio/CHANGE_RADIOS";
5 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 |
3 | export {
4 | reducer
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from "immutable";
2 | import * as actionTypes from './constants';
3 |
4 | const defaultState = Map({
5 | categories: [],
6 | currentId: 0,
7 | recommends: [],
8 | radios: []
9 | })
10 |
11 | export default (state = defaultState, action) => {
12 | switch(action.type) {
13 | case actionTypes.CHANGE_RADIO_CATEGORY:
14 | return state.set("categories", action.categories);
15 | case actionTypes.CHANGE_CURRENT_ID:
16 | return state.set("currentId", action.currentId);
17 | case actionTypes.CHANGE_RECOMMENDS:
18 | return state.set("recommends", action.recommends);
19 | case actionTypes.CHANGE_RADIOS:
20 | return state.set("radios", action.radios);
21 | default:
22 | return state;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/djradio/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const DjRadioWrapper = styled.div`
4 | border: 1px solid #d3d3d3;
5 | border-width: 0 1px;
6 | background-color: #fff;
7 | padding: 40px;
8 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/c-cpns/ranking-header/index.js:
--------------------------------------------------------------------------------
1 | import React, {memo} from 'react';
2 | import { useSelector, shallowEqual } from "react-redux";
3 |
4 | import { formatMonthDay } from "@/utils/format-utils";
5 |
6 | import HYSongOperationBar from '@/components/song-operation-bar';
7 | import {
8 | RankingHeaderWrapper
9 | } from './style';
10 |
11 | export default memo(function HYRankingHeader() {
12 | // redux
13 | const state = useSelector(state => ({
14 | playList: state.getIn(["ranking", "playList"]),
15 | }), shallowEqual);
16 | const topInfo = state.playList;
17 |
18 | return (
19 |
20 |
21 |

22 |
封面
23 |
24 |
25 |
{topInfo.name}
26 |
27 |
28 |
最近更新:{formatMonthDay(topInfo.updateTime)}
29 |
({"每日更新:TODO"})
30 |
31 |
35 |
36 |
37 | )
38 | })
39 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/c-cpns/ranking-header/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const RankingHeaderWrapper = styled.div`
4 | display: flex;
5 | align-items: center;
6 | padding: 40px;
7 |
8 | .image {
9 | padding: 3px;
10 | border: 1px solid #ccc;
11 | position: relative;
12 | img {
13 | width: 150px;
14 | height: 150px;
15 | }
16 |
17 | .image_cover {
18 | background-position: -230px -380px;
19 | }
20 | }
21 |
22 | .info {
23 | margin-left: 30px;
24 | .title {
25 | color: #333;
26 | font-size: 20px;
27 | font-family: "Microsoft Yahei", Arial, Helvetica, sans-serif;
28 | }
29 |
30 | .time {
31 | display: flex;
32 | align-items: center;
33 | color: #666;
34 | margin: 8px 0 30px;
35 |
36 | .clock {
37 | display: inline-block;
38 | width: 13px;
39 | height: 13px;
40 | background-position: -18px -682px;
41 | position: relative;
42 | top: -2px;
43 | margin-right: 3px;
44 | }
45 |
46 | .update-f {
47 | color: #999;
48 | }
49 | }
50 | }
51 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/c-cpns/ranking-list/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useSelector, shallowEqual } from "react-redux";
3 |
4 | import {
5 | getSizeImage,
6 | formatMinuteSecond
7 | } from "@/utils/format-utils.js"
8 |
9 | import HYThemeHeaderSong from '@/components/theme-header-song';
10 | import {
11 | RankingListWrapper
12 | } from './style';
13 |
14 | export default memo(function HYRankingList() {
15 | const state = useSelector(state => ({
16 | playList: state.getIn(["ranking", "playList"])
17 | }), shallowEqual);
18 | const tracks = state.playList.tracks || [];
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 | |
28 | 标题 |
29 | 时长 |
30 | 歌手 |
31 |
32 |
33 |
34 | {
35 | tracks.map((item, index) => {
36 | return (
37 |
38 |
39 |
40 | {index + 1}
41 |
42 |
43 | |
44 |
45 |
46 | {
47 | index < 3 ?
48 | (  ) : null
49 | }
50 |
51 | {item.name}
52 |
53 | |
54 | {formatMinuteSecond(item.dt)} |
55 | {item.ar[0].name} |
56 |
57 | )
58 | })
59 | }
60 |
61 |
62 |
63 |
64 | )
65 | })
66 |
67 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/c-cpns/ranking-list/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RankingListWrapper = styled.div`
4 | padding: 0 40px;
5 |
6 | .play-list {
7 | table {
8 | width: 100%;
9 | border: 1px solid #d9d9d9;
10 |
11 | thead {
12 | th {
13 | height: 34px;
14 | line-height: 34px;
15 | background-image: url(${require("@/assets/img/sprite_table.png")});
16 | color: #666;
17 | border: 1px solid #ddd;
18 | border-width: 0 0 1px 1px;
19 | padding-left: 10px;
20 | }
21 |
22 | .ranking {
23 | width: 78px;
24 | border-left: none;
25 | }
26 |
27 | .duration {
28 | width: 91px;
29 | }
30 |
31 | .singer {
32 | width: 173px;
33 | }
34 | }
35 |
36 | tbody {
37 | td {
38 | padding: 6px 10px;
39 | }
40 |
41 | tr:nth-child(2n) {
42 | background-color: #fff;
43 | }
44 |
45 | tr:nth-child(2n+1) {
46 | background-color: #f7f7f7;
47 | }
48 |
49 | .rank-num {
50 | display: flex;
51 |
52 | .num {
53 | width: 25px;
54 | height: 18px;
55 | text-align: center;
56 | color: #999;
57 | }
58 |
59 | .new {
60 | width: 16px;
61 | height: 17px;
62 | margin-left: 12px;
63 | background-position: -67px -283px;
64 | }
65 | }
66 |
67 | .song-name {
68 | display: flex;
69 | align-items: center;
70 | img {
71 | width: 50px;
72 | height: 50px;
73 | margin-right: 10px;
74 | }
75 |
76 | .play {
77 | width: 17px;
78 | height: 17px;
79 | background-position: 0 -103px;
80 | }
81 |
82 | .name {
83 | margin-left: 10px;
84 | }
85 | }
86 | }
87 | }
88 | }
89 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/c-cpns/top-ranking/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import classNames from "classnames";
3 | import { useSelector, useDispatch, shallowEqual } from "react-redux";
4 |
5 | import { getSizeImage } from "@/utils/format-utils";
6 | import {
7 | changeCurrentIndex,
8 | getRanking
9 | } from "../../store/actionCreators"
10 |
11 | import {
12 | TopRankingWrapper
13 | } from "./style";
14 |
15 | export default memo(function HYTopRanking() {
16 | // redux
17 | const state = useSelector(state => ({
18 | topList: state.getIn(["ranking", "topList"]),
19 | currentIndex: state.getIn(["ranking", "currentIndex"])
20 | }), shallowEqual);
21 | const currentIndex = state.currentIndex;
22 | const dispatch = useDispatch();
23 |
24 | // hooks
25 | useEffect(() => {
26 | const id = (state.topList[currentIndex] && state.topList[currentIndex].id);
27 | if (!id) return;
28 | dispatch(getRanking(id))
29 | }, [state, dispatch, currentIndex])
30 |
31 | // handle function
32 | const hanldeItemClick = (index) => {
33 | dispatch(changeCurrentIndex(index));
34 | const id = state.topList[currentIndex].id;
35 | dispatch(getRanking(id))
36 | }
37 |
38 | return (
39 |
40 | {
41 | state.topList.map((item, index) => {
42 | let header;
43 | if (index === 0 || index === 4) {
44 | header = {index === 0 ? "云音乐特色榜" : "全球媒体榜"}
45 | }
46 | return (
47 |
48 | {header}
49 |
hanldeItemClick(index)}>
51 |

52 |
53 |
{item.name}
54 |
{item.updateFrequency}
55 |
56 |
57 |
58 | )
59 | })
60 | }
61 |
62 | )
63 | })
64 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/c-cpns/top-ranking/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TopRankingWrapper = styled.div`
4 | padding: 25px 0;
5 |
6 | .header {
7 | padding: 12px 12px 10px;
8 | font-size: 14px;
9 | color: #000;
10 | font-family: simsun;
11 | }
12 |
13 | .item {
14 | height: 62px;
15 | padding-left: 20px;
16 | display: flex;
17 | align-items: center;
18 | cursor: pointer;
19 |
20 | &:hover, &.active {
21 | background-color: #e6e6e6;
22 | }
23 |
24 | img {
25 | width: 40px;
26 | height: 40px;
27 | }
28 |
29 | .info {
30 | margin-left: 10px;
31 |
32 | .name {
33 | color: #000;
34 | }
35 |
36 | .update {
37 | margin-top: 5px;
38 | color: #999;
39 | }
40 | }
41 | }
42 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import { useDispatch } from "react-redux";
3 |
4 | import { getTops } from "./store/actionCreators";
5 |
6 | import HYTopRanking from "./c-cpns/top-ranking";
7 | import HYRankingHeader from './c-cpns/ranking-header';
8 | import HYRankingList from './c-cpns/ranking-list';
9 | import {
10 | RankingWrapper,
11 | RankingLeft,
12 | RankingRight,
13 | } from "./style";
14 |
15 | export default memo(function HYRanking() {
16 | // redux
17 | const dispatch = useDispatch();
18 |
19 | // hooks
20 | useEffect(() => {
21 | dispatch(getTops());
22 | }, [dispatch])
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | )
35 | })
36 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from "./constants";
2 |
3 | import {
4 | getTopList,
5 | getRankingList
6 | } from "@/services/ranking";
7 |
8 |
9 | const changeTopListAction = (res) => ({
10 | type: actionTypes.CHANGE_TOP_LIST,
11 | topList: res.list
12 | })
13 |
14 | const changePlayListAction = (res) => ({
15 | type: actionTypes.CHANGE_PLAY_LIST,
16 | playList: res.playlist
17 | })
18 |
19 | export const changeCurrentIndex = (index) => ({
20 | type: actionTypes.CHANGE_CURRENT_INDEX,
21 | currentIndex: index
22 | })
23 |
24 | export const getTops = () => {
25 | return dispatch => {
26 | getTopList().then(res => {
27 | dispatch(changeTopListAction(res));
28 | })
29 | }
30 | }
31 |
32 | export const getRanking = (id) => {
33 | return dispatch => {
34 | getRankingList(id).then(res => {
35 | dispatch(changePlayListAction(res))
36 | })
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_TOP_LIST = "ranking/CHANGE_TOP_LIST";
2 | export const CHANGE_CURRENT_INDEX = "rangking/CHANGE_CURRENT_INDEX";
3 | export const CHANGE_PLAY_LIST = "ranking/CHANGE_PLAY_LIST";
4 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from "./reducer";
2 |
3 | export {
4 | reducer
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from "immutable";
2 | import * as actionTypes from './constants';
3 |
4 | const defaultState = Map({
5 | topList: [],
6 | currentIndex: 0,
7 | playList: {}
8 | })
9 |
10 | export default (state = defaultState, action) => {
11 | switch(action.type) {
12 | case actionTypes.CHANGE_TOP_LIST:
13 | return state.set("topList", action.topList);
14 | case actionTypes.CHANGE_CURRENT_INDEX:
15 | return state.set("currentIndex", action.currentIndex);
16 | case actionTypes.CHANGE_PLAY_LIST:
17 | return state.set("playList", action.playList);
18 | default:
19 | return state;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/ranking/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const RankingWrapper = styled.div`
4 | display: flex;
5 | `
6 |
7 | export const RankingLeft = styled.div`
8 | width: 240px;
9 | border: 1px solid #d3d3d3;
10 | border-width: 0 1px;
11 | `
12 |
13 | export const RankingRight = styled.div`
14 | width: 740px;
15 | border-right: 1px solid #d3d3d3;
16 | background-color: #fafafa;
17 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/hot-radio/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import {
4 | hotRadios
5 | } from "@/services/local-data";
6 |
7 | import HYThemeHeaderSmall from '@/components/theme-header-small';
8 | import {
9 | HotRadioWrapper
10 | } from './style';
11 |
12 | export default memo(function HYHotRadio() {
13 | return (
14 |
15 |
16 |
17 | {
18 | hotRadios.map((item, index) => {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
{item.name}
26 |
{item.position}
27 |
28 |
29 | )
30 | })
31 | }
32 |
33 |
34 | )
35 | })
36 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/hot-radio/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const HotRadioWrapper = styled.div`
4 | padding: 20px;
5 |
6 | .radio-list {
7 | margin-top: 20px;
8 |
9 | .item {
10 | display: flex;
11 | margin-bottom: 10px;
12 | width: 210px;
13 | .image {
14 | img {
15 | width: 40px;
16 | height: 40px;
17 | }
18 | }
19 |
20 | .info {
21 | width: 160px;
22 | margin-left: 8px;
23 | .name {
24 | color: #000;
25 | font-weight: 700;
26 | margin-top: 3px;
27 | }
28 |
29 | .position {
30 | color: #666;
31 | }
32 | }
33 | }
34 | }
35 | `
36 |
37 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/hot-recommend/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo, useCallback } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from "react-redux";
3 | import { useHistory } from 'react-router-dom';
4 |
5 | import {
6 | getRecommend
7 | } from "../../store/actionCreators";
8 |
9 | import {
10 | RecommendWrapper
11 | } from "./style";
12 |
13 | import HYThemeHeaderRCM from '@/components/theme-header-rcm';
14 | import HYThemeCover from '@/components/theme-cover';
15 |
16 | export default memo(function HYHotRecommend() {
17 | // redux
18 | const state = useSelector(state => ({
19 | recommends: state.getIn(["recommend", "hotRecommends"])
20 | }), shallowEqual);
21 | const dispatch = useDispatch();
22 | const history = useHistory();
23 |
24 | useEffect(() => {
25 | dispatch(getRecommend())
26 | }, [dispatch]);
27 |
28 | const keywordClick = useCallback((keyword) => {
29 | history.push({pathname: "/discover/songs", cat: keyword});
30 | }, [history]);
31 |
32 | return (
33 |
34 |
38 |
39 | {
40 | state.recommends.slice(0, 8).map((item, index) => {
41 | return (
42 |
43 | )
44 | })
45 | }
46 |
47 |
48 | )
49 | })
50 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/hot-recommend/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const RecommendWrapper = styled.div`
4 | .recommend-list {
5 | display: flex;
6 | flex-wrap: wrap;
7 | justify-content: space-between;
8 | }
9 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/new-album/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, memo } from 'react';
2 | import { useSelector, useDispatch, shallowEqual } from 'react-redux';
3 |
4 | import {
5 | getAlbum
6 | } from "../../store/actionCreators";
7 |
8 | import { Carousel } from 'antd';
9 | import HYThemeHeaderRCM from '@/components/theme-header-rcm';
10 | import HYAlbumCover from "@/components/album-cover";
11 | import {
12 | AlbumWrapper
13 | } from "./style";
14 |
15 | export default memo(function HYNewAlbum(props) {
16 | // redux
17 | const state = useSelector(state => ({
18 | newAlbum: state.getIn(["recommend", "newAlbum"])
19 | }), shallowEqual);
20 | const dispatch = useDispatch();
21 |
22 | // hooks
23 | const carouselRef = useRef();
24 | useEffect(() => {
25 | dispatch(getAlbum());
26 | }, [dispatch]);
27 |
28 | return (
29 |
30 |
31 |
32 |
carouselRef.current.prev()}>
34 |
35 |
36 | {
37 | [0, 1].map(item => {
38 | return (
39 |
40 | {
41 | state.newAlbum.slice(item*5, (item+1)*5).map(item => {
42 | return (
43 |
44 | )
45 | })
46 | }
47 |
48 | )
49 | })
50 | }
51 |
52 |
53 |
carouselRef.current.next()}>
55 |
56 |
57 | )
58 | })
59 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/new-album/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const AlbumWrapper = styled.div`
4 | margin-top: 50px;
5 |
6 | .content {
7 | height: 186px;
8 | background-color: #f5f5f5;
9 | border: 1px solid #d3d3d3;
10 | margin: 20px 0 37px;
11 | display: flex;
12 | align-items: center;
13 |
14 | .arrow {
15 | width: 30px;
16 | height: 17px;
17 | cursor: pointer;
18 | }
19 |
20 | .arrow-left {
21 | background-position: -260px -75px;
22 | }
23 |
24 | .arrow-right {
25 | background-position: -300px -75px;
26 | }
27 |
28 | .album {
29 | width: 640px;
30 | height: 150px;
31 |
32 | .ant-carousel .slick-slide {
33 | height: 150px;
34 | overflow: hidden;
35 | }
36 |
37 | .page {
38 | display: flex !important;
39 | justify-content: space-between;
40 | align-items: center;
41 | }
42 | }
43 | }
44 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/ranking-list/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from "react-redux";
3 |
4 | import {
5 | getTopData
6 | } from "../../store/actionCreators";
7 |
8 | import HYThemeHeaderRCM from '@/components/theme-header-rcm';
9 | import HYTopRanking from "@/components/top-ranking";
10 | import {
11 | RankingWrapper
12 | } from "./style";
13 |
14 | export default memo(function HYRankingList() {
15 | // redux
16 | const dispatch = useDispatch();
17 | const state = useSelector((state) => ({
18 | topUpList: state.getIn(["recommend", "topUpList"]),
19 | topNewList: state.getIn(["recommend", "topNewList"]),
20 | topOriginList: state.getIn(["recommend", "topOriginList"])
21 | }), shallowEqual);
22 |
23 | // hooks
24 | useEffect(() => {
25 | dispatch(getTopData(19723756));
26 | dispatch(getTopData(3779629));
27 | dispatch(getTopData(2884035));
28 | }, [dispatch])
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | })
41 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/ranking-list/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const RankingWrapper = styled.div`
4 | .tops {
5 | margin: 30px 0;
6 | display: flex;
7 | background-image: url(${require("@/assets/img/recommend-top-bg.png")});
8 | height: 472px;
9 | }
10 | `
11 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/settle-singer/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 |
4 | import {
5 | getSettleSingers
6 | } from "../../store/actionCreators";
7 | import {
8 | getSizeImage
9 | } from "@/utils/format-utils";
10 |
11 | import HYThemeHeaderSmall from '@/components/theme-header-small';
12 | import {
13 | SetterSongerWrapper
14 | } from "./style";
15 |
16 | export default memo(function HYSettleSinger() {
17 | // redux
18 | const dispatch = useDispatch();
19 | const state = useSelector((state) => ({
20 | settleSings: state.getIn(["recommend", "settleSings"])
21 | }), shallowEqual);
22 |
23 | // hooks
24 | useEffect(() => {
25 | dispatch(getSettleSingers());
26 | }, [dispatch])
27 |
28 | return (
29 |
30 |
31 |
46 |
49 |
50 | )
51 | })
52 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/settle-singer/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const SetterSongerWrapper = styled.div`
4 | padding: 20px;
5 |
6 | .singer-list {
7 | .item {
8 | display: flex;
9 | height: 62px;
10 | margin-top: 14px;
11 | background-color: #fafafa;
12 | text-decoration: none;
13 |
14 | :hover {
15 | background-color: #f4f4f4;
16 | }
17 |
18 | img {
19 | width: 62px;
20 | height: 62px;
21 | }
22 |
23 | .info {
24 | margin: 8px 0 0 10px;
25 | .title {
26 | color: #333;
27 | font-size: 14px;
28 | font-weight: 700;
29 | }
30 |
31 | .name {
32 | margin-top: 5px;
33 | }
34 | }
35 | }
36 | }
37 |
38 | .apply-for {
39 | margin-top: 12px;
40 | a {
41 | color: #333;
42 | font-weight: 700;
43 | text-align: center;
44 | display: block;
45 | height: 31px;
46 | line-height: 31px;
47 | border-radius: 4px;
48 | background-color: #fafafa;
49 | border: 1px solid #c3c3c3;
50 | text-decoration: none;
51 | }
52 | }
53 | `
54 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/top-banner/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect, useCallback, useState, useRef } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 |
4 | import {
5 | getBanner
6 | } from '../../store/actionCreators';
7 |
8 | import { Carousel } from 'antd';
9 | import {
10 | BannerWrapper,
11 | BannerLeft,
12 | BannerRight,
13 | BannerControl
14 | } from './style';
15 |
16 | export default memo(function HYTopBanner() {
17 | const [currentIndex, setCurrentIndex] = useState(0);
18 |
19 | const dispatch = useDispatch();
20 | const state = useSelector(state => ({
21 | banners: state.getIn(["recommend", "topBanners"])
22 | }), shallowEqual)
23 |
24 | const bannerRef = useRef();
25 | useEffect(() => {
26 | dispatch(getBanner());
27 | }, [dispatch]);
28 |
29 | const bannerChange = useCallback((from, to) => {
30 | setTimeout(() => {
31 | setCurrentIndex(from);
32 | }, 0);
33 | }, []);
34 |
35 | const bgImage = state.banners[currentIndex] && (state.banners[currentIndex].imageUrl + "?imageView&blur=40x20")
36 |
37 | return (
38 |
39 |
40 |
41 |
42 | {
43 | state.banners.map((item, index) => {
44 | return (
45 |
46 |

47 |
48 | )
49 | })
50 | }
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | )
62 | })
63 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/top-banner/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const BannerWrapper = styled.div`
4 | background: url(${props => props.bgImage}) center center/6000px;
5 |
6 | .banner {
7 | height: 270px;
8 | background-color: red;
9 |
10 | display: flex;
11 | position: relative;
12 | }
13 | `
14 |
15 | export const BannerLeft = styled.div`
16 | width: 730px;
17 |
18 | .banner-item {
19 | overflow: hidden;
20 | height: 270px;
21 | .image {
22 | width: 100%;
23 | }
24 | }
25 | `
26 |
27 | export const BannerRight = styled.a.attrs({
28 | href: "https://music.163.com/#/download",
29 | target: "_blank"
30 | })`
31 | width: 254px;
32 | height: 270px;
33 | background: url(${require("@/assets/img/download.png")});
34 | `
35 |
36 | export const BannerControl = styled.div`
37 | position: absolute;
38 | left: 0;
39 | right: 0;
40 | top: 50%;
41 | transform: translateY(-50%);
42 |
43 | .btn {
44 | position: absolute;
45 | width: 37px;
46 | height: 63px;
47 | background-image: url(${require("@/assets/img/banner_sprite.png")});
48 | background-color: transparent;
49 | cursor: pointer;
50 |
51 | &:hover {
52 | background-color: rgba(0, 0, 0, .1);
53 | }
54 | }
55 |
56 | .left {
57 | left: -68px;
58 | background-position: 0 -360px;
59 | }
60 |
61 | .right {
62 | right: -68px;
63 | background-position: 0 -508px;
64 | }
65 | `
66 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/user-login/index.js:
--------------------------------------------------------------------------------
1 | import React, {memo} from 'react';
2 |
3 | import {
4 | UserLoginWrapper
5 | } from "./style";
6 |
7 | export default memo(function HYUserLogin() {
8 | return (
9 |
10 | 登录网易云音乐,可以享受无限收藏的乐趣,并且无限同步到手机
11 | 用户登录
12 |
13 | )
14 | })
15 |
16 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/c-cpns/user-login/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const UserLoginWrapper = styled.div`
4 | height: 126px;
5 | background-position: 0 0;
6 | padding: 16px 22px;
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 |
11 | p {
12 | line-height: 25px;
13 | }
14 |
15 | a {
16 | margin-top: 10px;
17 | display: inline-block;
18 | width: 100px;
19 | height: 31px;
20 | line-height: 31px;
21 | text-align: center;
22 | color: #fff;
23 | text-decoration: none;
24 | background-position: 0 -195px;
25 | text-shadow: 0 1px 0 #8a060b;
26 |
27 | :hover {
28 | background-position: -110px -195px;
29 | }
30 | }
31 | `
32 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import HYTopBanner from './c-cpns/top-banner';
4 | import HYHotRecommend from './c-cpns/hot-recommend';
5 | import HYNewAlbum from './c-cpns/new-album';
6 | import HYRankingList from './c-cpns/ranking-list';
7 | import HYUserLogin from './c-cpns/user-login';
8 | import HYSettleSinger from './c-cpns/settle-singer';
9 | import HYHotRadio from './c-cpns/hot-radio';
10 | import {
11 | RecommendWraper,
12 | Content,
13 | RecommendLeft,
14 | RecommendRight
15 | } from "./style";
16 |
17 | export default memo(function HYRecommend() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | )
35 | })
36 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from "./constants";
2 |
3 | import {
4 | getTopBanner,
5 | getHotRecommend,
6 | getNewAlbum,
7 | getTopList,
8 | getArtistList
9 | } from "@/services/recommend";
10 |
11 | const changeBannerAction = (res) => ({
12 | type: actionTypes.CHANGE_TOP_BNNAER,
13 | banners: res.banners
14 | })
15 |
16 | const changeRecommendAction = (res) => ({
17 | type: actionTypes.CHANGE_HOT_RECOMMEND,
18 | recommends: res.result
19 | })
20 |
21 | const changeNewAlbumAction = (res) => ({
22 | type: actionTypes.CHANGE_NEW_ALBUM,
23 | newAlbum: res.albums
24 | })
25 |
26 | const changeUpListAction = (res) => ({
27 | type: actionTypes.CHANGE_UP_LIST,
28 | topUpList: res.playlist
29 | })
30 |
31 | const changeNewListAction = (res) => ({
32 | type: actionTypes.CHANGE_NEW_LIST,
33 | topNewList: res.playlist
34 | })
35 |
36 | const changeOriginListAction = (res) => ({
37 | type: actionTypes.CHANGE_ORIGIN_LIST,
38 | topOriginList: res.playlist
39 | })
40 |
41 | const changeSettleSingsAction = (res) => ({
42 | type: actionTypes.CHANGE_SETTLE_SONGER,
43 | settleSings: res.artists
44 | })
45 |
46 |
47 | export const getBanner = () => {
48 | return dispatch => {
49 | getTopBanner().then(res => {
50 | dispatch(changeBannerAction(res));
51 | })
52 | }
53 | }
54 |
55 | export const getRecommend = () => {
56 | return dispatch => {
57 | getHotRecommend().then(res => {
58 | dispatch(changeRecommendAction(res))
59 | })
60 | }
61 | }
62 |
63 | export const getAlbum = () => {
64 | return dispatch => {
65 | getNewAlbum(10, 0).then(res => {
66 | dispatch(changeNewAlbumAction(res))
67 | })
68 | }
69 | }
70 |
71 | export const getTopData = (idx) => {
72 | return dispatch => {
73 | getTopList(idx).then(res => {
74 | switch (idx) {
75 | case 19723756:
76 | dispatch(changeNewListAction(res));
77 | break;
78 | case 3779629:
79 | dispatch(changeOriginListAction(res));
80 | break;
81 | case 2884035:
82 | dispatch(changeUpListAction(res));
83 | break;
84 | default:
85 | console.log("其他数据处理");
86 | }
87 | })
88 | }
89 | }
90 |
91 | export const getSettleSingers = () => {
92 | return dispath => {
93 | getArtistList(5, 5001).then(res => {
94 | dispath(changeSettleSingsAction(res))
95 | })
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_TOP_BNNAER = "recommend/CHANGE_TOP_BNNAER";
2 | export const CHANGE_HOT_RECOMMEND = "recommend/CHANGE_HOT_RECOMMEND";
3 | export const CHANGE_NEW_ALBUM = "recommend/CHANGE_NEW_ALBUM";
4 | export const CHANGE_UP_LIST = "recommend/CHAGNE_UP_LIST";
5 | export const CHANGE_NEW_LIST = "recommend/CHANGE_NEW_LIST";
6 | export const CHANGE_ORIGIN_LIST = "recommend/CHANGE_ORIGIN_LIST";
7 |
8 | export const CHANGE_SETTLE_SONGER = "recommend/CHANGE_SETTLE_SONGER";
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from "./reducer";
2 |
3 | export {
4 | reducer
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from "immutable";
2 | import * as actionTypes from './constants';
3 |
4 | const defaultState = Map({
5 | topBanners: [],
6 | hotRecommends: [],
7 | newAlbum: [],
8 | topUpList: {},
9 | topNewList: {},
10 | topOriginList: {},
11 |
12 | settleSings: [],
13 | })
14 |
15 | export default (state = defaultState, action) => {
16 | switch(action.type) {
17 | case actionTypes.CHANGE_TOP_BNNAER:
18 | return state.set("topBanners", action.banners);
19 | case actionTypes.CHANGE_HOT_RECOMMEND:
20 | return state.set("hotRecommends", action.recommends);
21 | case actionTypes.CHANGE_NEW_ALBUM:
22 | return state.set("newAlbum", action.newAlbum);
23 | case actionTypes.CHANGE_UP_LIST:
24 | return state.set("topUpList", action.topUpList);
25 | case actionTypes.CHANGE_NEW_LIST:
26 | return state.set("topNewList", action.topNewList);
27 | case actionTypes.CHANGE_ORIGIN_LIST:
28 | return state.set("topOriginList", action.topOriginList);
29 | case actionTypes.CHANGE_SETTLE_SONGER:
30 | return state.set("settleSings", action.settleSings)
31 | default:
32 | return state;
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/recommend/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RecommendWraper = styled.div`
4 |
5 | `
6 |
7 | export const Content = styled.div`
8 | background-color: #fff;
9 | display: flex;
10 | `
11 |
12 | export const RecommendLeft = styled.div`
13 | padding: 20px;
14 | width: 729px;
15 | `
16 |
17 | export const RecommendRight = styled.div`
18 | width: 250px;
19 | border: 1px solid #d3d3d3;
20 | border-width: 0 1px;
21 | `
22 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/c-cpns/songs-category/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useSelector, useDispatch, shallowEqual } from "react-redux";
3 |
4 | import {
5 | changeCurrentCategoryAction,
6 | getSongList
7 | } from "../../store/actionCreators";
8 |
9 | import {
10 | CategoryWrapper
11 | } from "./style";
12 |
13 | export default memo(function HYSongsCategory() {
14 | // redux
15 | const { category } = useSelector(state => ({
16 | category: state.getIn(["songs", "category"])
17 | }), shallowEqual);
18 | const dispatch = useDispatch();
19 |
20 | function selectCategory(name) {
21 | dispatch(changeCurrentCategoryAction(name));
22 | dispatch(getSongList(0));
23 | }
24 |
25 | return (
26 |
27 |
28 |
29 | selectCategory("全部")}>全部风格
30 |
31 |
32 | {
33 | category.map((item, index) => {
34 | return (
35 |
36 | -
37 |
38 | {item.name}
39 |
40 | -
41 | {
42 | item.subs.map(sItem => {
43 | return (
44 |
45 | selectCategory(sItem.name)}>{sItem.name}
46 | |
47 |
48 | )
49 | })
50 | }
51 |
52 |
53 | )
54 | })
55 | }
56 |
57 |
58 | )
59 | })
60 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/c-cpns/songs-category/style.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const CategoryWrapper = styled.div`
4 | position: absolute;
5 | z-index: 99;
6 | top: 55px;
7 | left: -25px;
8 | width: 700px;
9 | border: 1px solid #ccc;
10 | background-color: #fefefe;
11 | box-shadow: 0 0 10px 2px #d3d3d3;
12 | border-radius: 5px;
13 | padding-top: 10px;
14 |
15 | .arrow {
16 | position: absolute;
17 | top: -11px;
18 | left: 110px;
19 | width: 24px;
20 | height: 11px;
21 | background-position: -48px 0;
22 | }
23 |
24 | .all {
25 | padding: 10px 25px;
26 | border-bottom: 1px solid #e2e2e2;
27 | a {
28 | display: inline-block;
29 | text-align: center;
30 | width: 75px;
31 | height: 26px;
32 | line-height: 26px;
33 | border: 1px solid #d3d3d3;
34 | border-radius: 3px;
35 | background-color: #fafafa;
36 | }
37 | }
38 |
39 | .category {
40 | padding-left: 25px;
41 | dl {
42 | display: flex;
43 | align-items: flex-start;
44 | }
45 |
46 | dt {
47 | display: inline-flex;
48 | align-items: center;
49 | padding: 15px 0 10px;
50 | width: 70px;
51 | text-align: center;
52 |
53 | i {
54 | display: inline-block;
55 | width: 24px;
56 | height: 24px;
57 | background-position: -20px -735px;
58 | margin-right: 8px;
59 | }
60 | }
61 |
62 | dl.item1 {
63 | i {
64 | background-position: 0 -60px;
65 | }
66 | }
67 |
68 | dl.item2 {
69 | i {
70 | background-position: 0 -88px;
71 | }
72 | }
73 |
74 | dl.item3 {
75 | i {
76 | background-position: 0 -117px;
77 | }
78 | }
79 |
80 | dl.item4 {
81 | i {
82 | background-position: 0 -141px;
83 | }
84 |
85 | dd {
86 | padding-bottom: 25px;
87 | }
88 | }
89 |
90 | dd {
91 | padding-top: 18px;
92 | padding-left: 15px;
93 | flex: 1;
94 | display: flex;
95 | flex-wrap: wrap;
96 | border-left: 1px solid #e2e2e2;
97 |
98 | .item {
99 | margin-bottom: 8px;
100 | }
101 |
102 | a {
103 | color: #333;
104 | }
105 |
106 | .divider {
107 | margin: 0 12px;
108 | color: #e2e2e2;
109 | }
110 | }
111 | }
112 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/c-cpns/songs-header/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, memo } from 'react';
2 | import { useSelector, shallowEqual } from 'react-redux';
3 |
4 | import HYSongsCategory from '../songs-category'
5 | import {
6 | HeaderWrapper,
7 | HeaderLeft,
8 | HeaderRight
9 | } from "./style";
10 |
11 | export default memo(function HYSongsHeader() {
12 | // hooks
13 | const [showCategory, setShowCategory] = useState(false);
14 |
15 | // redux
16 | const { currentCategory } = useSelector(state => ({
17 | currentCategory: state.getIn(["songs", "currentCategory"])
18 | }), shallowEqual);
19 |
20 | return (
21 |
22 |
23 | {currentCategory}
24 |
28 | {showCategory ? : null}
29 |
30 |
31 |
32 |
33 |
34 | )
35 | })
36 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/c-cpns/songs-header/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HeaderWrapper = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | border-bottom: 2px solid #c20c0c;
8 | padding-bottom: 6px;
9 | `
10 |
11 | export const HeaderLeft = styled.div`
12 | display: flex;
13 | align-items: center;
14 | position: relative;
15 | .title {
16 | font-size: 24px;
17 | font-family: "Microsoft Yahei", Arial, Helvetica, sans-serif;
18 | }
19 |
20 | .select {
21 | position: relative;
22 | top: 2px;
23 | width: 91px;
24 | height: 31px;
25 | line-height: 31px;
26 | background-color: #fafafa;
27 | border: 1px solid #d3d3d3;
28 | border-radius: 3px;
29 | color: #0c73c2;
30 | margin-left: 10px;
31 | cursor: pointer;
32 | &:hover {
33 | background-color: #fff;
34 | }
35 |
36 | i {
37 | position: relative;
38 | left: 5px;
39 | bottom: 2px;
40 | display: inline-block;
41 | width: 8px;
42 | height: 5px;
43 | background-position: -70px -543px;
44 | }
45 | }
46 |
47 | .show {
48 | /* display: block !important; */
49 | }
50 |
51 | .category {
52 | display: block;
53 | .cover {
54 | position: fixed;
55 | left: 0;
56 | right: 0;
57 | top: 105px;
58 | bottom: 0;
59 | background-color: rgba(0,0,0,.05);
60 | }
61 | }
62 | `
63 |
64 | export const HeaderRight = styled.div`
65 | .hot {
66 | width: 46px;
67 | height: 29px;
68 | background-color: #c20c0c;
69 | color: #fff;
70 | border-radius: 3px;
71 | border: 1px solid #aaa;
72 | }
73 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/c-cpns/songs-list/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, memo } from 'react';
2 | import { useSelector, useDispatch, shallowEqual } from "react-redux";
3 |
4 | import { PER_PAGE_NUMBER } from '../../store/constants';
5 | import { getSongList } from "../../store/actionCreators";
6 |
7 | import HYThemeCover from '@/components/theme-cover';
8 | import HYPagination from '@/components/pagination';
9 | import {
10 | SongListWrapper
11 | } from "./style";
12 |
13 | export default memo(function HYSongsList() {
14 | // hooks
15 | const [currentPage, setCurrentPage] = useState(1);
16 |
17 | // redux
18 | const { categorySongs } = useSelector(state => ({
19 | categorySongs: state.getIn(["songs", "categorySongs"])
20 | }), shallowEqual);
21 | const songList = categorySongs.playlists || [];
22 | const total = categorySongs.total || 0;
23 | const dispatch = useDispatch();
24 |
25 | function onPageChange(page, pageSize) {
26 | setCurrentPage(page);
27 | dispatch(getSongList(page));
28 | }
29 |
30 | return (
31 |
32 |
33 | {
34 | songList.map((item, index) => {
35 | return (
36 |
37 | )
38 | })
39 | }
40 |
41 |
45 |
46 | )
47 | })
48 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/c-cpns/songs-list/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const SongListWrapper = styled.div`
4 | .songs-list {
5 | display: flex;
6 | flex-wrap: wrap;
7 | justify-content: space-between;
8 | margin-right: -30px;
9 | }
10 | `
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, memo } from 'react';
2 | import { useDispatch } from "react-redux";
3 | import { useLocation } from 'react-router-dom';
4 |
5 | import {
6 | getCategory,
7 | getSongList,
8 | changeCurrentCategoryAction
9 | } from "./store/actionCreators";
10 |
11 | import HYSongsHeader from "./c-cpns/songs-header";
12 | import HYSongsList from './c-cpns/songs-list';
13 | import {
14 | SongsWrapper
15 | } from "./style"
16 |
17 | export default memo(function HYSongs() {
18 | // redux
19 | const dispatch = useDispatch();
20 | const cat = useLocation().cat;
21 |
22 | useEffect(() => {
23 | dispatch(changeCurrentCategoryAction(cat));
24 | }, [dispatch, cat]);
25 |
26 | // hooks
27 | useEffect(() => {
28 | dispatch(getCategory());
29 | dispatch(getSongList(0));
30 | }, [dispatch])
31 |
32 | return (
33 |
34 |
35 |
36 |
37 | )
38 | })
39 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './constants';
2 | import { PER_PAGE_NUMBER } from './constants';
3 |
4 | import {
5 | getSongCategory,
6 | getSongCategoryList
7 | } from "@/services/songs";
8 | import {
9 | handleSongsCategory
10 | } from "@/utils/handle-data";
11 |
12 | const changeCategoryAction = (res) => ({
13 | type: actionTypes.CHANGE_CATEGORY,
14 | category: res
15 | })
16 |
17 | const changeSongListAction = (res) => ({
18 | type: actionTypes.CHANGE_CATEGORY_SONGS,
19 | categorySongs: res
20 | })
21 |
22 | export const changeCurrentCategoryAction = (name) => ({
23 | type: actionTypes.CHANGE_CURRENT_CATEGORY,
24 | currentCategory: name
25 | })
26 |
27 | export const getCategory = () => {
28 | return dispatch => {
29 | getSongCategory().then(res => {
30 | const categoryData = handleSongsCategory(res);
31 | dispatch(changeCategoryAction(categoryData))
32 | })
33 | }
34 | }
35 |
36 | export const getSongList = (page) => {
37 | return (dispatch, getState) => {
38 | // 1.获取currentCategory
39 | const name = getState().getIn(["songs", "currentCategory"]);
40 |
41 | // 2.获取数据
42 | getSongCategoryList(name, page * PER_PAGE_NUMBER).then(res => {
43 | dispatch(changeSongListAction(res));
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_CATEGORY = "songs/CHANGE_CATEGORY";
2 | export const CHANGE_CURRENT_CATEGORY = "songs/CHANGE_CURRENT_CATEGORY";
3 | export const CHANGE_CATEGORY_SONGS = "songs/CHANGE_CATEGORY_SONGS";
4 |
5 | export const PER_PAGE_NUMBER = 35;
6 |
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 |
3 | export {
4 | reducer
5 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from "immutable";
2 | import * as actionTypes from "./constants";
3 |
4 | const defaultState = Map({
5 | category: [],
6 | currentCategory: "全部",
7 | categorySongs: {}
8 | })
9 |
10 | export default (state = defaultState, action) => {
11 | switch(action.type) {
12 | case actionTypes.CHANGE_CATEGORY:
13 | return state.set("category", action.category);
14 | case actionTypes.CHANGE_CURRENT_CATEGORY:
15 | return state.set("currentCategory", action.currentCategory);
16 | case actionTypes.CHANGE_CATEGORY_SONGS:
17 | return state.set("categorySongs", action.categorySongs);
18 | default:
19 | return state;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/pages/discover/c-pages/songs/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const SongsWrapper = styled.div`
4 | padding: 40px;
5 | border: 1px solid #d3d3d3;
6 | border-width: 0 1px;
7 | background-color: #fff;
8 | `
--------------------------------------------------------------------------------
/src/pages/discover/index.js:
--------------------------------------------------------------------------------
1 | import React, {memo} from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import { renderRoutes } from "react-router-config";
4 |
5 | import {
6 | dicoverMenu
7 | } from "@/services/local-data";
8 |
9 | import {
10 | DiscoverWrapper,
11 | TopMenu
12 | } from "./style";
13 |
14 | export default memo(function HYDiscover(props) {
15 | const { route } = props;
16 |
17 | return (
18 |
19 |
20 |
21 | {
22 | dicoverMenu.map((item, index) => {
23 | return (
24 |
25 | {item.title}
26 |
27 | );
28 | })
29 | }
30 |
31 |
32 | {renderRoutes(route.routes)}
33 |
34 | )
35 | })
36 |
--------------------------------------------------------------------------------
/src/pages/discover/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const DiscoverWrapper = styled.div`
4 | .top {
5 | height: 30px;
6 | background-color: #C20C0C;
7 | }
8 | `
9 |
10 | export const TopMenu = styled.div`
11 | display: flex;
12 | padding-left: 180px;
13 | position: relative;
14 | top: -4px;
15 |
16 | .item {
17 | a {
18 | display: inline-block;
19 | height: 20px;
20 | line-height: 20px;
21 | padding: 0 13px;
22 | margin: 7px 17px 0;
23 | color: #fff;
24 |
25 | &:hover, &.active {
26 | text-decoration: none;
27 | background-color: #9B0909;
28 | border-radius: 20px;
29 | }
30 | }
31 | }
32 | `
--------------------------------------------------------------------------------
/src/pages/friend/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { FriendWrapper } from './style';
4 |
5 | export default memo(function HYFriend() {
6 | return (
7 |
8 |
13 |
14 | )
15 | })
16 |
--------------------------------------------------------------------------------
/src/pages/friend/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const FriendWrapper = styled.div`
4 | .content {
5 | background-color: #fff;
6 | min-height: 700px;
7 |
8 | .pic {
9 | position: relative;
10 | width: 807px;
11 | height: 484px;
12 | margin: 0 auto;
13 | background: url(${require("@/assets/img/friend_sprite.jpg")}) 0 104px no-repeat;
14 |
15 | .login {
16 | position: absolute;
17 | width: 167px;
18 | height: 45px;
19 | left: 482px;
20 | top: 368px;
21 | text-indent: -9999px;
22 | }
23 | }
24 | }
25 | `
--------------------------------------------------------------------------------
/src/pages/main/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, Suspense } from 'react';
2 | import { HashRouter } from "react-router-dom";
3 | import { renderRoutes } from "react-router-config";
4 |
5 | import routes from "@/router"
6 |
7 | import HYAppHeader from '@/components/app-header';
8 | import HYAppFooter from '@/components/app-footer';
9 | import HYAppPlayBar from '@/pages/player/app-play-bar';
10 |
11 | export default memo(function HYMain() {
12 | return (
13 |
14 |
15 | loading}>
16 | {renderRoutes(routes)}
17 |
18 |
19 |
20 |
21 | )
22 | })
23 |
--------------------------------------------------------------------------------
/src/pages/mine/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import { MineWrapper } from './style';
4 |
5 | export default memo(function HYMine() {
6 | return (
7 |
8 |
13 |
14 | )
15 | })
16 |
--------------------------------------------------------------------------------
/src/pages/mine/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const MineWrapper = styled.div`
4 | .content {
5 | background-color: #fff;
6 | min-height: 700px;
7 |
8 | .pic {
9 | position: relative;
10 | width: 807px;
11 | height: 372px;
12 | margin: 0 auto;
13 | background: url(${require("@/assets/img/mine_sprite.png")}) 0 104px no-repeat;
14 |
15 | .login {
16 | position: absolute;
17 | width: 167px;
18 | height: 45px;
19 | left: 482px;
20 | top: 302px;
21 | text-indent: -9999px;
22 | }
23 | }
24 | }
25 | `
--------------------------------------------------------------------------------
/src/pages/player/app-play-bar/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useRef, useEffect, useState, useCallback } from 'react';
2 | import { useSelector, shallowEqual, useDispatch } from 'react-redux';
3 |
4 | import { message } from 'antd';
5 |
6 | import { getPlayUrl, formatMinuteSecond } from '@/utils/format-utils';
7 | import {
8 | getSongDetailAction,
9 | changeCurrentLyricIndexAction,
10 | changePlaySequenceAction,
11 | changePlaySongAction
12 | } from '../store/actionCreators';
13 |
14 | import HYAppPlayPanel from '../app-play-panel'
15 | import { NavLink } from 'react-router-dom';
16 | import { Slider } from 'antd';
17 | import {
18 | PlaybarWrapper,
19 | Control,
20 | PlayInfo,
21 | Operator
22 | } from './style';
23 |
24 | export default memo(function HYAppPlaybar() {
25 | // props and state
26 | const [isPlaying, setIsPlaying] = useState(false);
27 | const [duration, setDuration] = useState(0);
28 | const [currentTime, setCurrentTime] = useState(0);
29 | const [progress, setProgress] = useState(0);
30 | const [isChanging, setIsChanging] = useState(false);
31 | const [showPanel, setShowPanel] = useState(false);
32 |
33 | // redux hooks
34 | const {
35 | currentSong,
36 | currentLyrics,
37 | currentLyricIndex,
38 | playList,
39 | playSequence
40 | } = useSelector(state => ({
41 | currentSong: state.getIn(["player", "currentSong"]),
42 | currentLyrics: state.getIn(["player", "currentLyrics"]),
43 | currentLyricIndex: state.getIn(["player", "currentLyricIndex"]),
44 | playList: state.getIn(["player", "playList"]),
45 | playSequence: state.getIn(["player", "playSequence"])
46 | }), shallowEqual);
47 | const dispatch = useDispatch();
48 |
49 | // other hooks
50 | const audioRef = useRef();
51 | useEffect(() => {
52 | dispatch(getSongDetailAction(167876));
53 | }, [dispatch]);
54 |
55 | useEffect(() => {
56 | audioRef.current.src = getPlayUrl(currentSong.id);
57 | audioRef.current.play().then(res => {
58 | setIsPlaying(true);
59 | }).catch(err => {
60 | setIsPlaying(false);
61 | });
62 | setDuration(currentSong.dt);
63 | }, [currentSong]);
64 |
65 | // 其他业务
66 | const play = useCallback(() => {
67 | setIsPlaying(!isPlaying);
68 | isPlaying ? audioRef.current.pause() : audioRef.current.play().catch(err => {
69 | setIsPlaying(false);
70 | });
71 | }, [isPlaying]);
72 |
73 | const timeUpdate = (e) => {
74 | const currentTime = e.target.currentTime;
75 | if (!isChanging) {
76 | setCurrentTime(currentTime);
77 | setProgress((currentTime * 1000) / duration * 100);
78 | }
79 |
80 | let lrcLength = currentLyrics.length;
81 | let i = 0;
82 | for (; i < lrcLength; i++) {
83 | const lrcTime = currentLyrics[i].time;
84 | if (currentTime * 1000 < lrcTime) {
85 | break
86 | }
87 | }
88 | const finalIndex = i - 1;
89 | if (finalIndex !== currentLyricIndex) {
90 | dispatch(changeCurrentLyricIndexAction(finalIndex));
91 | message.open({
92 | content: currentLyrics[finalIndex].content,
93 | key: "lyric",
94 | duration: 0,
95 | className: 'lyric-message',
96 | })
97 | }
98 | }
99 |
100 | const timeEnded = () => {
101 | if (playSequence === 2 || playList.length === 1) {
102 | audioRef.current.currentTime = 0;
103 | audioRef.current.play();
104 | } else {
105 | dispatch(changePlaySongAction(1));
106 | }
107 | }
108 |
109 | const sliderChange = useCallback((value) => {
110 | setProgress(value);
111 | const time = value / 100.0 * duration / 1000;
112 | setCurrentTime(time);
113 | setIsChanging(true);
114 | }, [duration])
115 |
116 | const sliderAfterChange = useCallback((value) => {
117 | const time = value / 100.0 * duration / 1000;
118 | audioRef.current.currentTime = time;
119 | setCurrentTime(time);
120 | setIsChanging(false);
121 |
122 | if (!isPlaying) {
123 | play();
124 | }
125 | }, [duration, isPlaying, play]);
126 |
127 | return (
128 |
129 |
130 |
131 |
133 |
134 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | {currentSong.name}
146 | {currentSong.ar[0].name}
147 |
148 |
149 |
150 |
151 | {formatMinuteSecond(currentTime * 1000)}
152 | /
153 | {formatMinuteSecond(duration)}
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
167 |
171 |
172 |
173 |
174 |
177 | )
178 | })
179 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-bar/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PlaybarWrapper = styled.div`
4 | position: fixed;
5 | z-index: 99;
6 | left: 0;
7 | right: 0;
8 | bottom: 0;
9 | height: 52px;
10 | background-position: 0 0;
11 | background-repeat: repeat;
12 |
13 | .content {
14 | display: flex;
15 | align-items: center;
16 | justify-content: space-between;
17 | position: absolute;
18 | left: 50%;
19 | transform: translateX(-50%);
20 | bottom: 0;
21 | height: 47px;
22 | }
23 | `
24 |
25 | export const Control = styled.div`
26 | display: flex;
27 | align-items: center;
28 |
29 | .prev, .next {
30 | width: 28px;
31 | height: 28px;
32 | }
33 |
34 | .prev {
35 | background-position: 0 -130px;
36 | }
37 |
38 | .play {
39 | width: 36px;
40 | height: 36px;
41 | margin: 0 8px;
42 | background-position: 0 ${props => props.isPlaying ? "-165px": "-204px"};
43 | }
44 |
45 | .next {
46 | background-position: -80px -130px;
47 | }
48 | `
49 |
50 | export const PlayInfo = styled.div`
51 | display: flex;
52 | width: 642px;
53 | align-items: center;
54 |
55 | .image {
56 | width: 34px;
57 | height: 34px;
58 | border-radius: 5px;
59 | }
60 |
61 | .info {
62 | flex: 1;
63 | color: #a1a1a1;
64 | margin-left: 10px;
65 |
66 | .song {
67 | color: #e1e1e1;
68 | position: relative;
69 | top: 8px;
70 | left: 8px;
71 |
72 | .singer-name {
73 | color: #a1a1a1;
74 | margin-left: 10px;
75 | }
76 | }
77 |
78 | .progress {
79 | display: flex;
80 | align-items: center;
81 |
82 | .ant-slider {
83 | width: 493px;
84 | margin-right: 10px;
85 |
86 | .ant-slider-rail {
87 | height: 9px;
88 | background: url(${require("@/assets/img/progress_bar.png")}) right 0;
89 | }
90 |
91 | .ant-slider-track {
92 | height: 9px;
93 | background: url(${require("@/assets/img/progress_bar.png")}) left -66px;
94 | }
95 |
96 | .ant-slider-handle {
97 | width: 22px;
98 | height: 24px;
99 | border: none;
100 | margin-top: -7px;
101 | background: url(${require("@/assets/img/sprite_icon.png")}) 0 -250px;
102 | }
103 | }
104 |
105 | .time {
106 | .now-time {
107 | color: #e1e1e1;
108 | }
109 | .divider {
110 | margin: 0 3px;
111 | }
112 | }
113 | }
114 | }
115 |
116 | `
117 |
118 | export const Operator = styled.div`
119 | display: flex;
120 | position: relative;
121 | top: 5px;
122 |
123 | .btn {
124 | width: 25px;
125 | height: 25px;
126 | }
127 |
128 | .favor {
129 | background-position: -88px -163px;
130 | }
131 |
132 | .share {
133 | background-position: -114px -163px;
134 | }
135 |
136 | .right {
137 | display: flex;
138 | align-items: center;
139 | width: 126px;
140 | padding-left: 13px;
141 | background-position: -147px -248px;
142 |
143 | .volume {
144 | background-position: -2px -248px;
145 | }
146 |
147 | .loop {
148 | background-position: ${props => {
149 | switch(props.sequence) {
150 | case 1:
151 | return "-66px -248px";
152 | case 2:
153 | return "-66px -344px";
154 | default:
155 | return "-3px -344px";
156 | }
157 | }};
158 | }
159 |
160 | .playlist {
161 | padding-left: 18px;
162 | text-align: center;
163 | color: #ccc;
164 | width: 59px;
165 | background-position: -42px -68px;
166 | }
167 | }
168 | `
169 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/c-cpns/lyric-panel/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useRef, useEffect } from 'react';
2 | import { useSelector, shallowEqual } from 'react-redux';
3 | import classNames from 'classnames';
4 |
5 | import { scrollTo } from "@/utils/ui-helper";
6 |
7 | import { PannelWrapper } from './style';
8 |
9 | export default memo(function HYLyricPanel() {
10 | const { currentLyrics, currentLyricIndex } = useSelector(state => ({
11 | currentLyrics: state.getIn(["player", "currentLyrics"]),
12 | currentLyricIndex: state.getIn(["player", "currentLyricIndex"])
13 | }), shallowEqual);
14 |
15 | // other hooks
16 | const panelRef = useRef();
17 | useEffect(() => {
18 | if (currentLyricIndex > 0 && currentLyricIndex < 3) return;
19 | scrollTo(panelRef.current, (currentLyricIndex - 3) * 32, 300)
20 | }, [currentLyricIndex]);
21 |
22 | return (
23 |
24 |
25 | {
26 | currentLyrics.map((item, index) => {
27 | return (
28 |
30 | {item.content}
31 |
32 | )
33 | })
34 | }
35 |
36 |
37 | )
38 | })
39 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/c-cpns/lyric-panel/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PannelWrapper = styled.div`
4 | position: relative;
5 | flex: 1;
6 | margin: 21px 0 20px 0;
7 | overflow: scroll;
8 |
9 | &::-webkit-scrollbar {
10 | display: none;
11 | }
12 |
13 | .lrc-content {
14 | .lrc-item {
15 | height: 32px;
16 | text-align: center;
17 | color: #989898;
18 |
19 | &.active {
20 | color: #fff;
21 | font-size: 14px;
22 | }
23 | }
24 | }
25 |
26 |
27 | `
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/c-cpns/play-header/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useSelector, shallowEqual } from 'react-redux';
3 |
4 | import {
5 | HeaderWrapper,
6 | HeaderLeft,
7 | HeaderRight
8 | } from './style';
9 |
10 | export default memo(function HYPlayHeader() {
11 | const { playList, currentSong } = useSelector(state => ({
12 | playList: state.getIn(["player", "playList"]),
13 | currentSong: state.getIn(["player", "currentSong"])
14 | }), shallowEqual);
15 |
16 | return (
17 |
18 |
19 | 播放列表({playList.length})
20 |
21 |
25 |
29 |
30 |
31 |
32 | {currentSong.name}
33 |
34 |
35 | )
36 | })
37 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/c-cpns/play-header/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HeaderWrapper = styled.div`
4 | display: flex;
5 | height: 41px;
6 | line-height: 41px;
7 | background: url(${require("@/assets/img/playpanel_bg.png")}) 0 0;
8 | `
9 |
10 | export const HeaderLeft = styled.div`
11 | display: flex;
12 | justify-content: space-between;
13 | width: 553px;
14 | padding: 0 25px;
15 |
16 | h3 {
17 | color: #e2e2e2;
18 | font-weight: 700;
19 | }
20 |
21 | .operator {
22 | color: #ccc;
23 |
24 | button {
25 | background-color: transparent;
26 | color: #ccc;
27 | }
28 |
29 | .icon {
30 | display: inline-block;
31 | width: 16px;
32 | height: 16px;
33 | position: relative;
34 | top: 4px;
35 | right: 2px;
36 | }
37 |
38 | .favor {
39 | background-position: -24px 0;
40 | }
41 |
42 | .remove {
43 | width: 13px;
44 | background-position: -51px 0;
45 | }
46 | }
47 | `
48 |
49 | export const HeaderRight = styled.div`
50 | flex: 1;
51 | text-align: center;
52 | color: #fff;
53 | font-size: 14px;
54 | `
55 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/c-cpns/play-list/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { useSelector, shallowEqual } from 'react-redux';
3 | import classNames from 'classnames';
4 |
5 | import { formatMinuteSecond } from '@/utils/format-utils';
6 |
7 | import { PlayListWrapper } from './style';
8 |
9 | export default memo(function HYPlayList() {
10 | const { playList, currentSongIndex } = useSelector(state => ({
11 | playList: state.getIn(["player", "playList"]),
12 | currentSongIndex: state.getIn(["player", "currentSongIndex"])
13 | }), shallowEqual);
14 |
15 | return (
16 |
17 | {
18 | playList.map((item, index) => {
19 | return (
20 |
22 |
{item.name}
23 |
24 | {item.ar[0].name}
25 | {formatMinuteSecond(item.dt)}
26 |
27 |
28 |
29 | )
30 | })
31 | }
32 |
33 | )
34 | })
35 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/c-cpns/play-list/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PlayListWrapper = styled.div`
4 | position: relative;
5 | width: 553px;
6 | padding: 2px;
7 |
8 | .play-item {
9 | padding: 0 8px 0 25px;
10 | display: flex;
11 | position: relative;
12 | justify-content: space-between;
13 | align-items: center;
14 | height: 28px;
15 | line-height: 28px;
16 | color: #ccc;
17 |
18 | &.active {
19 | color: #fff;
20 | background-color: #000;
21 |
22 | ::before {
23 | content: "";
24 | position: absolute;
25 | left: 8px;
26 | width: 10px;
27 | height: 13px;
28 | background: url(${require("@/assets/img/playlist_sprite.png")}) -182px 0;
29 | }
30 | }
31 |
32 | .right {
33 | display: flex;
34 | align-items: center;
35 |
36 | .singer {
37 | width: 80px;
38 | }
39 |
40 | .duration {
41 | width: 45px;
42 | }
43 |
44 | .link {
45 | margin-left: 20px;
46 | width: 14px;
47 | height: 16px;
48 | background-position: -100px 0;
49 | }
50 | }
51 | }
52 | `
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import HYPlayHeader from './c-cpns/play-header';
4 | import HYPlayList from './c-cpns/play-list';
5 | import HYLyricPanel from './c-cpns/lyric-panel';
6 | import { PanelWrapper } from './style';
7 |
8 | export default memo(function HYAppPlayList() {
9 | return (
10 |
11 |
12 |
13 |

14 |
15 |
16 |
17 |
18 | )
19 | })
20 |
--------------------------------------------------------------------------------
/src/pages/player/app-play-panel/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PanelWrapper = styled.div`
4 | position: absolute;
5 | left: 50%;
6 | bottom: 46px;
7 | transform: translateX(-50%);
8 | width: 986px;
9 | height: 301px;
10 | color: #e2e2e2;
11 |
12 | .main {
13 | position: relative;
14 | display: flex;
15 | height: 260px;
16 | overflow: hidden;
17 | background: url(${require("@/assets/img/playpanel_bg.png")}) -1014px 0 repeat-y;
18 |
19 | .image {
20 | position: absolute;
21 | left: 2px;
22 | top: -360px;
23 | width: 980px;
24 | height: auto;
25 | opacity: .2;
26 | }
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-comment/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 |
3 | export default memo(function HYPlayerComment() {
4 | return (
5 |
6 |
7 |
8 | )
9 | })
10 |
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-info/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useState } from 'react';
2 | import { useSelector, shallowEqual } from 'react-redux';
3 |
4 | import { getSizeImage } from '@/utils/format-utils';
5 |
6 | import HYSongOperationBar from '@/components/song-operation-bar';
7 | import {
8 | InfoWrapper,
9 | InfoLeft,
10 | InfoRight
11 | } from './style';
12 |
13 | export default memo(function HYPlayerInfo() {
14 | // props and hooks
15 | const [isSpread, setIsSpread] = useState(false);
16 |
17 | // redux hooks
18 | const { currentSong, currentLyrics } = useSelector(state => ({
19 | currentSong: state.getIn(["player", "currentSong"]),
20 | currentLyrics: state.getIn(["player", "currentLyrics"])
21 | }), shallowEqual);
22 |
23 | // handle code
24 | const totalLyricCount = isSpread ? currentLyrics.length : 13;
25 |
26 | return (
27 |
28 |
29 |
30 |

31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
{currentSong.name}
42 |
43 |
47 |
51 |
52 |
56 |
57 |
58 |
59 | {
60 | currentLyrics.slice(0, totalLyricCount).map((item, index) => {
61 | return
{item.content}
62 | })
63 | }
64 |
65 |
70 |
71 |
72 |
73 | )
74 | })
75 |
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-info/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const InfoWrapper = styled.div`
4 | display: flex;
5 | padding: 47px 30px 40px 39px;
6 | `
7 |
8 | export const InfoLeft = styled.div`
9 | width: 206px;
10 |
11 | .image {
12 | display: flex;
13 | position: relative;
14 | justify-content: center;
15 | align-items: center;
16 | width: 198px;
17 | height: 198px;
18 |
19 | .cover {
20 | background-position: -140px -580px;
21 | width: 206px;
22 | height: 205px;
23 | top: -4px;
24 | left: -4px;
25 | }
26 | }
27 |
28 | .link {
29 | margin: 20px;
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | i {
34 | display: inline-block;
35 | width: 16px;
36 | height: 16px;
37 | background-position: -34px -863px;
38 | }
39 |
40 | a {
41 | color: #0c73c2;
42 | text-decoration: underline;
43 | }
44 | }
45 | `
46 |
47 | export const InfoRight = styled.div`
48 | width: 414px;
49 | margin-left: 20px;
50 |
51 | .header {
52 | display: flex;
53 | align-items: center;
54 | i {
55 | display: inline-block;
56 | width: 54px;
57 | height: 24px;
58 | background-position: 0 -463px;
59 | }
60 |
61 | .title {
62 | font-size: 24px;
63 | font-weight: 400;
64 | margin-left: 10px;
65 | }
66 | }
67 |
68 | .singer, .album {
69 | margin: 10px;
70 |
71 | a {
72 | color: #0c73c2;
73 | }
74 | }
75 |
76 | .lyric {
77 | padding: 30px 0 50px;
78 |
79 | .lyric-info {
80 | .text {
81 | margin: 6px 0;
82 | }
83 | }
84 |
85 | .lyric-control {
86 | position: relative;
87 | color: #0c73c2;
88 | background-color: #fff;
89 | text-decoration: underline;
90 | cursor: pointer;
91 |
92 | i {
93 | position: absolute;
94 | width: 11px;
95 | height: 8px;
96 | right: -8px;
97 | top: 2px;
98 | background-position: ${props => props.isSpread ? "-45px": "-65px"} -520px;
99 | }
100 | }
101 | }
102 | `
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-relevant/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 |
4 | import { getSimiSongAction } from '../../store/actionCreators';
5 |
6 | import HYThemeHeaderPlayer from '@/components/theme-header-player';
7 | import { RelevantWrapper } from './style';
8 |
9 | export default memo(function HYRelevant() {
10 | const { simiSongs } = useSelector(state => ({
11 | simiSongs: state.getIn(["player", "simiSongs"])
12 | }), shallowEqual);
13 | const dispatch = useDispatch();
14 |
15 | useEffect(() => {
16 | dispatch(getSimiSongAction());
17 | }, [dispatch]);
18 |
19 | return (
20 |
21 |
22 |
23 | {
24 | simiSongs.map((item, index) => {
25 | return (
26 |
27 |
35 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | }
43 |
44 |
45 | )
46 | })
47 |
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-relevant/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RelevantWrapper = styled.div`
4 | margin-top: 40px;
5 |
6 | .songs {
7 | .song-item {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | margin-bottom: 12px;
12 |
13 | .info {
14 | .title {
15 | a {
16 | color: #666;
17 | }
18 | }
19 |
20 | .artist {
21 | a {
22 | color: #999;
23 | }
24 | }
25 | }
26 |
27 | .operate {
28 | .item {
29 | display: inline-block;
30 | width: 10px;
31 | height: 11px;
32 | }
33 |
34 | .play {
35 | background-position: -69px -455px;
36 | margin-right: 10px;
37 | }
38 |
39 | .add {
40 | background-position: -87px -454px;
41 | }
42 | }
43 | }
44 | }
45 | `
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-songs/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect } from 'react';
2 | import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3 |
4 | import { getSizeImage } from '@/utils/format-utils';
5 |
6 | import HYThemeHeaderPlayer from '@/components/theme-header-player';
7 | import { PlayerSongsWrapper } from './style';
8 | import { getSimiPlaylistAction } from '../../store/actionCreators';
9 |
10 | export default memo(function HYPlayerSongs() {
11 |
12 | // redux hooks
13 | const { simiPlaylist } = useSelector(state => ({
14 | simiPlaylist: state.getIn(["player", "simiPlaylist"])
15 | }), shallowEqual);
16 | const dispatch = useDispatch();
17 |
18 | // other hooks
19 | useEffect(() => {
20 | dispatch(getSimiPlaylistAction())
21 | }, [dispatch]);
22 |
23 | return (
24 |
25 |
26 |
27 | {
28 | simiPlaylist.map((item, index) => {
29 | return (
30 |
42 | )
43 | })
44 | }
45 |
46 |
47 | )
48 | })
49 |
--------------------------------------------------------------------------------
/src/pages/player/c-cpns/player-songs/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PlayerSongsWrapper = styled.div`
4 | .songs {
5 | .song-item {
6 | display: flex;
7 | align-items: center;
8 | margin-top: 15px;
9 | width: 200px;
10 |
11 | .image {
12 | width: 50px;
13 | height: 50px;
14 | }
15 |
16 | .info {
17 | margin-left: 10px;
18 | .name {
19 | font-size: 14px;
20 | color: #000;
21 | }
22 |
23 | .auchor {
24 | color: #999;
25 |
26 | .nickname {
27 | color: #666;
28 | margin-left: 5px;
29 | }
30 | }
31 | }
32 | }
33 | }
34 | `
--------------------------------------------------------------------------------
/src/pages/player/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 |
3 | import HYPlayerInfo from './c-cpns/player-info';
4 | import HYPlayerComment from './c-cpns/player-comment';
5 | import HYPlayerSongs from './c-cpns/player-songs';
6 | import HYPlayerRelevant from './c-cpns/player-relevant';
7 | import {
8 | PlayerWrapper,
9 | PlayerLeft,
10 | PlayerRight
11 | } from './style';
12 |
13 | export default memo(function HYPlayer() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | })
29 |
--------------------------------------------------------------------------------
/src/pages/player/store/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './constants';
2 |
3 | import { getSongDetail, getLyric, getSimiPlaylist, getSimiSong } from '@/services/player';
4 | import { parseLyric } from '@/utils/lrc-parse';
5 |
6 | const changeCurrentSongAction = (song) => ({
7 | type: actionTypes.CHANGE_CURRENT_SONG,
8 | song
9 | });
10 |
11 | const changeCurrentSongIndexAction = (index) => ({
12 | type: actionTypes.CHANGE_CURRENT_SONG_INDEX,
13 | index
14 | });
15 |
16 | const changePlayListAction = (playList) => ({
17 | type: actionTypes.CHANGE_PLAY_LIST,
18 | playList: playList
19 | })
20 |
21 | const changeLyricsAction = (lyrics) => ({
22 | type: actionTypes.CHANGE_LYRICS,
23 | lyrics
24 | })
25 |
26 | const changeSimiPlaylistAction = (res) => ({
27 | type: actionTypes.CHANGE_SIMI_PLAYLIST,
28 | simiPlaylist: res.playlists
29 | })
30 |
31 | const changeSimiSongsAction = (res) => ({
32 | type: actionTypes.CHANGE_SIMI_SONGS,
33 | simiSongs: res.songs
34 | })
35 |
36 | export const changeCurrentLyricIndexAction = (index) => ({
37 | type: actionTypes.CHANGE_CURRENT_LYRIC_INDEX,
38 | index
39 | })
40 |
41 | export const changePlaySequenceAction = (currentSequence) => {
42 | if (currentSequence === 3) currentSequence = 0;
43 | return {
44 | type: actionTypes.CHANGE_PLAY_SEQUENCE,
45 | sequence: currentSequence
46 | }
47 | }
48 |
49 | export const changePlaySongAction = (tag) => {
50 | return (dispatch, getState) => {
51 | // 1.获取当前的index
52 | let currentSongIndex = getState().getIn(["player", "currentSongIndex"]);
53 | const playSequence = getState().getIn(["player", "playSequence"]);
54 | const playList = getState().getIn(["player", "playList"]);
55 |
56 | // 2.判断当前播放列表
57 | switch (playSequence) {
58 | case 1:
59 | currentSongIndex = Math.floor(Math.random() * playList.length);
60 | break;
61 | default:
62 | currentSongIndex += tag;
63 | if (currentSongIndex === playList.length) currentSongIndex = 0;
64 | if (currentSongIndex === -1) currentSongIndex = playList.length - 1;
65 | }
66 |
67 | // 3.处理修改数据
68 | const currentSong = playList[currentSongIndex];
69 | dispatch(changeCurrentSongIndexAction(currentSongIndex));
70 | dispatch(changeCurrentSongAction(currentSong));
71 |
72 | // 获取当前的歌词,并且解析
73 | getLyric(currentSong.id).then(res => {
74 | const lrcString = res.lrc.lyric;
75 | const lyrics = parseLyric(lrcString);
76 | dispatch(changeLyricsAction(lyrics));
77 | });
78 | }
79 | }
80 |
81 | export const getSongDetailAction = (ids) => {
82 | return (dispatch, getState) => {
83 | // 1.判断是否歌曲存在playList中
84 | const playList = getState().getIn(["player", "playList"]);
85 |
86 | const songIndex = playList.findIndex(song => song.id === ids);
87 | if (songIndex !== -1) { // 找到数据
88 | const currentSong = playList[songIndex];
89 | dispatch(changeCurrentSongIndexAction(songIndex));
90 | dispatch(changeCurrentSongAction(currentSong));
91 | } else { // 未找到数据
92 | getSongDetail(ids).then(res => {
93 | const song = res.songs && res.songs[0];
94 | if (!song) return;
95 | // 1.添加到playList中
96 | const newPlayList = [...playList];
97 | newPlayList.push(song);
98 | dispatch(changePlayListAction(newPlayList));
99 | // 2.改变当前index
100 | dispatch(changeCurrentSongIndexAction(newPlayList.length - 1));
101 | dispatch(changeCurrentSongAction(song));
102 | });
103 | }
104 |
105 | // 获取当前的歌词,并且解析
106 | getLyric(ids).then(res => {
107 | const lrcString = res.lrc.lyric;
108 | const lyrics = parseLyric(lrcString);
109 | dispatch(changeLyricsAction(lyrics));
110 | });
111 | }
112 | }
113 |
114 | export const getSimiPlaylistAction = () => {
115 | return (dispatch, getState) => {
116 | const id = getState().getIn(["player", "currentSong"]).id;
117 | if (!id) return;
118 |
119 | getSimiPlaylist(id).then(res => {
120 | dispatch(changeSimiPlaylistAction(res));
121 | })
122 | }
123 | }
124 |
125 | export const getSimiSongAction = () => {
126 | return (dispatch, getState) => {
127 | const id = getState().getIn(["player", "currentSong"]).id;
128 | if (!id) return;
129 |
130 | getSimiSong(id).then(res => {
131 | dispatch(changeSimiSongsAction(res));
132 | })
133 | }
134 | }
135 |
136 |
--------------------------------------------------------------------------------
/src/pages/player/store/constants.js:
--------------------------------------------------------------------------------
1 | export const CHANGE_CURRENT_SONG = "player/CHANGE_CURRENT_SONG";
2 | export const CHANGE_LYRICS = "player/CHANGE_LYRICS";
3 |
4 | export const CHANGE_SIMI_PLAYLIST = "player/CHANGE_SIMI_PLAYLIST";
5 | export const CHANGE_SIMI_SONGS = "player/CHANGE_SIMI_SONGS";
6 |
7 | export const CHANGE_PLAY_LIST = "player/CHANGE_PLAY_LIST";
8 | export const CHANGE_CURRENT_SONG_INDEX = "player/CHANGE_CURRENT_SONG_INDEX";
9 | export const CHANGE_PLAY_SEQUENCE = "player/CHANGE_PLAY_SEQUENCE";
10 |
11 | export const CHANGE_CURRENT_LYRIC_INDEX = "player/CHANGE_CURRENT_LYRIC_INDEX";
12 |
13 |
--------------------------------------------------------------------------------
/src/pages/player/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 | import { getSongDetailAction } from './actionCreators';
3 |
4 | export {
5 | reducer,
6 | getSongDetailAction
7 | }
--------------------------------------------------------------------------------
/src/pages/player/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 |
3 | import * as actionTypes from './constants';
4 |
5 | const defaultState = Map({
6 | playList: [
7 | {
8 | "name": "有何不可",
9 | "id": 167876,
10 | "pst": 0,
11 | "t": 0,
12 | "ar": [
13 | {
14 | "id": 5771,
15 | "name": "许嵩",
16 | "tns": [],
17 | "alias": []
18 | }
19 | ],
20 | "alia": [],
21 | "pop": 100,
22 | "st": 0,
23 | "rt": "600902000007916021",
24 | "fee": 8,
25 | "v": 49,
26 | "crbt": null,
27 | "cf": "",
28 | "al": {
29 | "id": 16953,
30 | "name": "自定义",
31 | "picUrl": "https://p1.music.126.net/Md3RLH0fe2a_3dMDnfqoQg==/18590542604286213.jpg",
32 | "tns": [],
33 | "pic_str": "18590542604286213",
34 | "pic": 18590542604286212
35 | },
36 | "dt": 241840,
37 | "h": {
38 | "br": 320000,
39 | "fid": 0,
40 | "size": 9675799,
41 | "vd": -21099
42 | },
43 | "m": {
44 | "br": 192000,
45 | "fid": 0,
46 | "size": 5805497,
47 | "vd": -18400
48 | },
49 | "l": {
50 | "br": 128000,
51 | "fid": 0,
52 | "size": 3870346,
53 | "vd": -16900
54 | },
55 | "a": null,
56 | "cd": "1",
57 | "no": 3,
58 | "rtUrl": null,
59 | "ftype": 0,
60 | "rtUrls": [],
61 | "djId": 0,
62 | "copyright": 2,
63 | "s_id": 0,
64 | "mark": 8192,
65 | "originCoverType": 0,
66 | "single": 0,
67 | "noCopyrightRcmd": null,
68 | "mv": 0,
69 | "mst": 9,
70 | "cp": 14026,
71 | "rtype": 0,
72 | "rurl": null,
73 | "publishTime": 1231516800000
74 | },
75 | {
76 | "name": "雅俗共赏",
77 | "id": 411214279,
78 | "pst": 0,
79 | "t": 0,
80 | "ar": [
81 | {
82 | "id": 5771,
83 | "name": "许嵩",
84 | "tns": [],
85 | "alias": []
86 | }
87 | ],
88 | "alia": [],
89 | "pop": 100,
90 | "st": 0,
91 | "rt": null,
92 | "fee": 8,
93 | "v": 31,
94 | "crbt": null,
95 | "cf": "",
96 | "al": {
97 | "id": 34749138,
98 | "name": "青年晚报",
99 | "picUrl": "https://p1.music.126.net/Wcs2dbukFx3TUWkRuxVCpw==/3431575794705764.jpg",
100 | "tns": [],
101 | "pic": 3431575794705764
102 | },
103 | "dt": 249621,
104 | "h": {
105 | "br": 320000,
106 | "fid": 0,
107 | "size": 9987177,
108 | "vd": -22200
109 | },
110 | "m": {
111 | "br": 192000,
112 | "fid": 0,
113 | "size": 5992323,
114 | "vd": -19600
115 | },
116 | "l": {
117 | "br": 128000,
118 | "fid": 0,
119 | "size": 3994896,
120 | "vd": -17800
121 | },
122 | "a": null,
123 | "cd": "1",
124 | "no": 2,
125 | "rtUrl": null,
126 | "ftype": 0,
127 | "rtUrls": [],
128 | "djId": 0,
129 | "copyright": 0,
130 | "s_id": 0,
131 | "mark": 8192,
132 | "originCoverType": 0,
133 | "single": 0,
134 | "noCopyrightRcmd": null,
135 | "mv": 5302271,
136 | "rtype": 0,
137 | "rurl": null,
138 | "mst": 9,
139 | "cp": 14026,
140 | "publishTime": 1461723397683
141 | }, {
142 | "name": "嚣张",
143 | "id": 1382596189,
144 | "pst": 0,
145 | "t": 0,
146 | "ar": [
147 | {
148 | "id": 32220939,
149 | "name": "en",
150 | "tns": [],
151 | "alias": []
152 | }
153 | ],
154 | "alia": [],
155 | "pop": 100,
156 | "st": 0,
157 | "rt": "",
158 | "fee": 8,
159 | "v": 10,
160 | "crbt": null,
161 | "cf": "",
162 | "al": {
163 | "id": 80816891,
164 | "name": "嚣张",
165 | "picUrl": "https://p2.music.126.net/NhkuFBphLFaSmYMeW1-frQ==/109951164271814514.jpg",
166 | "tns": [],
167 | "pic_str": "109951164271814514",
168 | "pic": 109951164271814510
169 | },
170 | "dt": 253994,
171 | "h": {
172 | "br": 320000,
173 | "fid": 0,
174 | "size": 10162605,
175 | "vd": -55669
176 | },
177 | "m": {
178 | "br": 192000,
179 | "fid": 0,
180 | "size": 6097581,
181 | "vd": -53082
182 | },
183 | "l": {
184 | "br": 128000,
185 | "fid": 0,
186 | "size": 4065069,
187 | "vd": -51369
188 | },
189 | "a": null,
190 | "cd": "01",
191 | "no": 1,
192 | "rtUrl": null,
193 | "ftype": 0,
194 | "rtUrls": [],
195 | "djId": 0,
196 | "copyright": 0,
197 | "s_id": 0,
198 | "mark": 0,
199 | "originCoverType": 0,
200 | "single": 0,
201 | "noCopyrightRcmd": null,
202 | "mv": 0,
203 | "rtype": 0,
204 | "rurl": null,
205 | "mst": 9,
206 | "cp": 1372818,
207 | "publishTime": 0
208 | }
209 | ],
210 | playSequence: 0, // 0 顺序播放 1 随机播放 2 单曲循环
211 | currentSongIndex: [],
212 | currentSong: {
213 | "name": "有何不可",
214 | "id": 167876,
215 | "pst": 0,
216 | "t": 0,
217 | "ar": [
218 | {
219 | "id": 5771,
220 | "name": "许嵩",
221 | "tns": [],
222 | "alias": []
223 | }
224 | ],
225 | "alia": [],
226 | "pop": 100,
227 | "st": 0,
228 | "rt": "600902000007916021",
229 | "fee": 8,
230 | "v": 49,
231 | "crbt": null,
232 | "cf": "",
233 | "al": {
234 | "id": 16953,
235 | "name": "自定义",
236 | "picUrl": "https://p2.music.126.net/Md3RLH0fe2a_3dMDnfqoQg==/18590542604286213.jpg",
237 | "tns": [],
238 | "pic_str": "18590542604286213",
239 | "pic": 18590542604286212
240 | },
241 | "dt": 241840,
242 | "h": {
243 | "br": 320000,
244 | "fid": 0,
245 | "size": 9675799,
246 | "vd": -21099
247 | },
248 | "m": {
249 | "br": 192000,
250 | "fid": 0,
251 | "size": 5805497,
252 | "vd": -18400
253 | },
254 | "l": {
255 | "br": 128000,
256 | "fid": 0,
257 | "size": 3870346,
258 | "vd": -16900
259 | },
260 | "a": null,
261 | "cd": "1",
262 | "no": 3,
263 | "rtUrl": null,
264 | "ftype": 0,
265 | "rtUrls": [],
266 | "djId": 0,
267 | "copyright": 2,
268 | "s_id": 0,
269 | "mark": 8192,
270 | "originCoverType": 0,
271 | "single": 0,
272 | "noCopyrightRcmd": null,
273 | "mv": 0,
274 | "rtype": 0,
275 | "rurl": null,
276 | "mst": 9,
277 | "cp": 14026,
278 | "publishTime": 1231516800000
279 | },
280 | currentLyrics: [],
281 | simiPlaylist: [],
282 | simiSongs: [],
283 | currentLyricIndex: -1
284 | });
285 |
286 | function reducer(state = defaultState, action) {
287 | switch (action.type) {
288 | case actionTypes.CHANGE_CURRENT_SONG:
289 | return state.set("currentSong", action.song);
290 | case actionTypes.CHANGE_LYRICS:
291 | return state.set("currentLyrics", action.lyrics);
292 | case actionTypes.CHANGE_SIMI_PLAYLIST:
293 | return state.set("simiPlaylist", action.simiPlaylist);
294 | case actionTypes.CHANGE_SIMI_SONGS:
295 | return state.set("simiSongs", action.simiSongs);
296 | case actionTypes.CHANGE_PLAY_LIST:
297 | return state.set("playList", action.playList);
298 | case actionTypes.CHANGE_CURRENT_SONG_INDEX:
299 | return state.set("currentSongIndex", action.index);
300 | case actionTypes.CHANGE_CURRENT_LYRIC_INDEX:
301 | return state.set("currentLyricIndex", action.index);
302 | case actionTypes.CHANGE_PLAY_SEQUENCE:
303 | return state.set("playSequence", action.sequence);
304 | default:
305 | return state;
306 | }
307 | }
308 |
309 | export default reducer;
310 |
--------------------------------------------------------------------------------
/src/pages/player/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PlayerWrapper = styled.div`
4 | .content {
5 | background: url(${require("@/assets/img/wrap-bg.png")}) repeat-y;
6 | background-color: #fff;
7 | display: flex;
8 | }
9 | `
10 |
11 | export const PlayerLeft = styled.div`
12 | width: 710px;
13 | `
14 |
15 | export const PlayerRight = styled.div`
16 | width: 270px;
17 | padding: 20px 40px 40px 30px;
18 | `
19 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect } from "react-router-dom";
3 |
4 | // import HYDiscover from "../pages/discover";
5 | // import HYRecommend from "../pages/discover/c-pages/recommend";
6 | // import HYRanking from "../pages/discover/c-pages/ranking";
7 | // import HYSongs from "../pages/discover/c-pages/songs";
8 | // import HYDjradio from "../pages/discover/c-pages/djradio";
9 | // import HYArtist from "../pages/discover/c-pages/artist";
10 | // import HYAlbum from "../pages/discover/c-pages/album";
11 | // import HYPlayer from "../pages/player";
12 |
13 | // import HYFriend from "../pages/friend";
14 | // import HYMine from "../pages/mine";
15 |
16 | const HYDiscover = React.lazy(_ => import("../pages/discover"));
17 | const HYRecommend = React.lazy(_ => import("../pages/discover/c-pages/recommend"));
18 | const HYRanking = React.lazy(_ => import("../pages/discover/c-pages/ranking"));
19 | const HYSongs = React.lazy(_ => import("../pages/discover/c-pages/songs"));
20 | const HYDjradio = React.lazy(_ => import("../pages/discover/c-pages/djradio"));
21 | const HYArtist = React.lazy(_ => import("../pages/discover/c-pages/artist"));
22 | const HYAlbum = React.lazy(_ => import("../pages/discover/c-pages/album"));
23 | const HYPlayer = React.lazy(_ => import("../pages/player"));
24 |
25 |
26 | const HYFriend = React.lazy(_ => import("../pages/friend"));
27 | const HYMine = React.lazy(_ => import("../pages/mine"));
28 |
29 |
30 | export default [
31 | {
32 | path: "/",
33 | exact: true,
34 | render: () => (
35 |
36 | )
37 | },
38 | {
39 | path: "/discover",
40 | component: HYDiscover,
41 | routes: [
42 | {
43 | path: "/discover",
44 | exact: true,
45 | render: () => (
46 |
47 | )
48 | },
49 | {
50 | path: "/discover/recommend",
51 | component: HYRecommend
52 | },
53 | {
54 | path: "/discover/ranking",
55 | component: HYRanking
56 | },
57 | {
58 | path: "/discover/songs",
59 | component: HYSongs
60 | },
61 | {
62 | path: "/discover/djradio",
63 | exact: true,
64 | component: HYDjradio
65 | },
66 | {
67 | path: "/discover/artist",
68 | component: HYArtist
69 | },
70 | {
71 | path: "/discover/album",
72 | component: HYAlbum
73 | },
74 | {
75 | path: "/discover/player",
76 | component: HYPlayer
77 | }
78 | ]
79 | },
80 | {
81 | path: "/friend",
82 | component: HYFriend
83 | },
84 | {
85 | path: "/mine",
86 | component: HYMine
87 | }
88 | ]
--------------------------------------------------------------------------------
/src/services/album.js:
--------------------------------------------------------------------------------
1 | import request from './axios';
2 |
3 | export function getHotAlbums() {
4 | return request({
5 | url: "/album/newest"
6 | })
7 | }
8 |
9 | export function getTopAlbums(limit, offset) {
10 | return request({
11 | url: "/album/new",
12 | params: {
13 | limit,
14 | offset
15 | }
16 | })
17 | }
--------------------------------------------------------------------------------
/src/services/artist.js:
--------------------------------------------------------------------------------
1 | import request from './axios';
2 |
3 | export function getArtistList(area, type, initial) {
4 | let url = "/artist/list";
5 | let params = { limit: 100 }
6 | if (area === -1 && type === 1) {
7 | url = "/top/artists"
8 | } else {
9 | if (area === -1) {
10 | params = {limit: 100, cat: 5001}
11 | } else {
12 | params = {
13 | type,
14 | area,
15 | initial,
16 | limit: 100
17 | }
18 | }
19 | }
20 |
21 | console.log("url:", url, "params:", params);
22 |
23 | return request({
24 | url,
25 | params
26 | })
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/src/services/axios.js:
--------------------------------------------------------------------------------
1 | import originAxios from 'axios';
2 |
3 | export default function request(option) {
4 | return new Promise((resolve, reject) => {
5 | // 1.创建axios的实例
6 | const instance = originAxios.create({
7 | baseURL: 'http://123.207.32.32:9002/',
8 | timeout: 10000
9 | });
10 |
11 | // 配置请求和响应拦截
12 | instance.interceptors.request.use(config => {
13 | // console.log('来到了request拦截success中');
14 | // 1.当发送网络请求时, 在页面中添加一个loading组件, 作为动画
15 |
16 | // 2.某些请求要求用户必须登录, 判断用户是否有token, 如果没有token跳转到login页面
17 |
18 | // 3.对请求的参数进行序列化(看服务器是否需要序列化)
19 | // config.data = qs.stringify(config.data)
20 | // console.log(config);
21 |
22 | // 4.等等
23 | return config
24 | }, err => {
25 | // console.log('来到了request拦截failure中');
26 | return err
27 | })
28 |
29 | instance.interceptors.response.use(response => {
30 | // console.log('来到了response拦截success中');
31 | return response.data
32 | }, err => {
33 | console.log('来到了response拦截failure中');
34 | console.log(err);
35 | if (err && err.response) {
36 | switch (err.response.status) {
37 | case 400:
38 | err.message = '请求错误'
39 | break
40 | case 401:
41 | err.message = '未授权的访问'
42 | break
43 | default:
44 | err.message = "其他错误信息"
45 | }
46 | }
47 | return err
48 | })
49 |
50 | // 2.传入对象进行网络请求
51 | instance(option).then(res => {
52 | resolve(res)
53 | }).catch(err => {
54 | reject(err)
55 | })
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/src/services/djradio.js:
--------------------------------------------------------------------------------
1 | import request from './axios';
2 |
3 | export function getDjRadioCatelist() {
4 | return request({
5 | url: "/dj/catelist"
6 | })
7 | }
8 |
9 | export function getDjRadioRecommend(type) {
10 | return request({
11 | url: "/dj/recommend/type",
12 | params: {
13 | type
14 | }
15 | })
16 | }
17 |
18 | export function getDjRadios(cateId, limit, offset) {
19 | return request({
20 | url: "/dj/radio/hot",
21 | params: {
22 | cateId,
23 | limit,
24 | offset
25 | }
26 | })
27 | }
--------------------------------------------------------------------------------
/src/services/local-data.js:
--------------------------------------------------------------------------------
1 | export const headerLinks = [
2 | {
3 | title: "发现音乐",
4 | link: "/discover"
5 | },
6 | {
7 | title: "我的音乐",
8 | link: "/mine"
9 | },
10 | {
11 | title: "朋友",
12 | link: "/friend"
13 | },
14 | {
15 | title: "商城",
16 | link: "https://music.163.com/store/product"
17 | },
18 | {
19 | title: "音乐人",
20 | link: "https://music.163.com/nmusician/web/index#/"
21 | },
22 | {
23 | title: "下载客户端",
24 | link: "https://music.163.com/#/download"
25 | }
26 | ]
27 |
28 |
29 | export const footerLinks = [
30 | {
31 | title: "服务条款",
32 | link: "https://st.music.163.com/official-terms/service"
33 | },
34 | {
35 | title: "隐私政策",
36 | link: "https://st.music.163.com/official-terms/privacy"
37 | },
38 | {
39 | title: "儿童隐私政策",
40 | link: "https://st.music.163.com/official-terms/children"
41 | },
42 | {
43 | title: "版权投诉指引",
44 | link: "https://music.163.com/st/staticdeal/complaints.html"
45 | },
46 | {
47 | title: "意见反馈",
48 | link: "#"
49 | }
50 | ]
51 |
52 | export const footerImages = [
53 | {
54 | link: "https://music.163.com/st/userbasic#/auth"
55 | },
56 | {
57 | link: "https://music.163.com/recruit"
58 | },
59 | {
60 | link: "https://music.163.com/web/reward"
61 | },
62 | {
63 | link: "https://music.163.com/uservideo#/plan"
64 | }
65 | ]
66 |
67 | // discover中的数据
68 | export const dicoverMenu = [
69 | {
70 | title: "推荐",
71 | link: "/discover/recommend"
72 | },
73 | {
74 | title: "排行榜",
75 | link: "/discover/ranking"
76 | },
77 | {
78 | title: "歌单",
79 | link: "/discover/songs"
80 | },
81 | {
82 | title: "主播电台",
83 | link: "/discover/djradio"
84 | },
85 | {
86 | title: "歌手",
87 | link: "/discover/artist"
88 | },
89 | {
90 | title: "新碟上架",
91 | link: "/discover/album"
92 | },
93 | ]
94 |
95 | // 热门主播
96 | export const hotRadios = [
97 | {
98 | picUrl: "http://p1.music.126.net/H3QxWdf0eUiwmhJvA4vrMQ==/1407374893913311.jpg",
99 | name: "陈立",
100 | position: "心理学家、美食家陈立教授",
101 | url: "/user/home?id=278438485"
102 | },
103 | {
104 | picUrl: "http://p1.music.126.net/y5-sM7tjnxnu_V9LWKgZlw==/7942872001461517.jpg",
105 | name: "DJ艳秋",
106 | position: "著名音乐节目主持人",
107 | url: "/user/home?id=91239965",
108 | },
109 | {
110 | picUrl: "http://p1.music.126.net/6cc6lgOfQTo6ovNnTHPyJg==/3427177769086282.jpg",
111 | name: "国家大剧院古典音乐频道",
112 | position: "国家大剧院古典音乐官方",
113 | url: "/user/home?id=324314596",
114 | },
115 | {
116 | picUrl: "http://p1.music.126.net/xa1Uxrrn4J0pm_PJwkGYvw==/3130309604335651.jpg",
117 | name: "谢谢收听",
118 | position: "南京电台主持人王馨",
119 | url: "/user/home?id=1611157",
120 | },
121 | {
122 | picUrl: "http://p1.music.126.net/slpd09Tf5Ju82Mv-h8MP4w==/3440371884651965.jpg",
123 | name: "DJ晓苏",
124 | position: "独立DJ,CRI环球旅游广播特邀DJ",
125 | url: "/user/home?id=2313954"
126 | }
127 | ]
128 |
129 | // 歌手类别
130 | export const artistCategories = [
131 | {
132 | title: "推荐",
133 | area: -1,
134 | artists: [
135 | {
136 | name: "推荐歌手",
137 | type: 1,
138 | url: "/discover/artist",
139 | id: 0
140 | },
141 | {
142 | name: "入驻歌手",
143 | type: 2,
144 | url: "/discover/artist?cat=5001",
145 | dataPath: "/artist/list?cat=5001"
146 | }
147 | ]
148 | },
149 | {
150 | title: "华语",
151 | area: 7,
152 | artists: [
153 | {
154 | name: "华语男歌手",
155 | url: "/discover/artist?id=1001",
156 | type: 1
157 | },
158 | {
159 | name: "华语女歌手",
160 | url: "/discover/artist?id=1002",
161 | type: 2
162 | },
163 | {
164 | name: "华语组合/乐队",
165 | url: "/discover/artist?id=1003",
166 | type: 3
167 | }
168 | ]
169 | },
170 | {
171 | title: "欧美",
172 | area: 96,
173 | artists: [
174 | {
175 | name: "欧美男歌手",
176 | url: "/discover/artist?id=2001",
177 | type: 1
178 | },
179 | {
180 | name: "欧美女歌手",
181 | url: "/discover/artist?id=2002",
182 | type: 2
183 | },
184 | {
185 | name: "欧美组合乐队",
186 | url: "/discover/artist?id=2003",
187 | type: 3
188 | },
189 | ]
190 | },
191 | {
192 | title: "日本",
193 | area: 8,
194 | artists: [
195 | {
196 | name: "日本男歌手",
197 | url: "/discover/artist?id=6001",
198 | type: 1
199 | },
200 | {
201 | name: "日本女歌手",
202 | url: "/discover/artist?id=6002",
203 | type: 2
204 | },
205 | {
206 | name: "日本组合/乐队",
207 | url: "/discover/artist?id=6003",
208 | type: 3
209 | },
210 | ]
211 | },
212 | {
213 | title: "韩国",
214 | area: 16,
215 | artists: [
216 | {
217 | name: "韩国男歌手",
218 | url: "/discover/artist?id=7001",
219 | type: 1
220 | },
221 | {
222 | name: "韩国女歌手",
223 | url: "/discover/artist?id=7002",
224 | type: 2
225 | },
226 | {
227 | name: "韩国组合/乐队",
228 | url: "/discover/artist?id=7003",
229 | type: 3
230 | },
231 | ]
232 | },
233 | {
234 | title: "其他",
235 | area: 0,
236 | artists: [
237 | {
238 | name: "其他男歌手",
239 | url: "/discover/artist?id=4001",
240 | type: 1
241 | },
242 | {
243 | name: "其他女歌手",
244 | url: "/discover/artist?id=4002",
245 | type: 2
246 | },
247 | {
248 | name: "其他组合乐队",
249 | url: "/discover/artist?id=4003",
250 | type: 3
251 | }
252 | ]
253 | },
254 | ]
255 |
--------------------------------------------------------------------------------
/src/services/player.js:
--------------------------------------------------------------------------------
1 | import request from './axios';
2 |
3 | export function getSongDetail(ids) {
4 | return request({
5 | url: "/song/detail",
6 | params: {
7 | ids
8 | }
9 | })
10 | }
11 |
12 | export function getLyric(id) {
13 | return request({
14 | url: "/lyric",
15 | params: {
16 | id
17 | }
18 | })
19 | }
20 |
21 | export function getSimiPlaylist(id) {
22 | return request({
23 | url: "/simi/playlist",
24 | params: {
25 | id
26 | }
27 | })
28 | }
29 |
30 | export function getSimiSong(id) {
31 | return request({
32 | url: "/simi/song",
33 | params: {
34 | id
35 | }
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/src/services/ranking.js:
--------------------------------------------------------------------------------
1 | import request from "./axios";
2 |
3 | export function getTopList() {
4 | return request({
5 | url: "/toplist"
6 | })
7 | }
8 |
9 | // 获取榜单详情
10 | export function getRankingList(id) {
11 | return request({
12 | url: "/playlist/detail",
13 | params: {
14 | id
15 | }
16 | })
17 | }
--------------------------------------------------------------------------------
/src/services/recommend.js:
--------------------------------------------------------------------------------
1 | import request from "./axios";
2 |
3 | export function getTopBanner() {
4 | return request({
5 | url: "/banner"
6 | })
7 | }
8 |
9 | export function getHotRecommend() {
10 | return request({
11 | url: "/personalized"
12 | })
13 | }
14 |
15 | export function getNewAlbum(limit, offset) {
16 | return request({
17 | url: "/album/new",
18 | params: {
19 | limit,
20 | offset
21 | }
22 | })
23 | }
24 |
25 | export function getTopList(id) {
26 | return request({
27 | url: "/playlist/detail",
28 | params: {
29 | id
30 | }
31 | })
32 | }
33 |
34 |
35 | export function getArtistList(limit, cat) {
36 | return request({
37 | url: "/artist/list",
38 | params: {
39 | cat,
40 | limit
41 | }
42 | })
43 | }
--------------------------------------------------------------------------------
/src/services/songs.js:
--------------------------------------------------------------------------------
1 | import request from "./axios";
2 |
3 | export function getSongCategory() {
4 | return request({
5 | url: "/playlist/catlist"
6 | })
7 | }
8 |
9 | export function getSongCategoryList(cat="全部", offset=0, limit = 35) {
10 | return request({
11 | url: "/top/playlist",
12 | params: {
13 | cat,
14 | limit,
15 | offset
16 | }
17 | })
18 | }
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from "redux";
2 | import thunk from "redux-thunk";
3 | import reducer from './reducer';
4 |
5 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
6 |
7 | const store = createStore(reducer, composeEnhancers(
8 | applyMiddleware(thunk)
9 | ));
10 |
11 | export default store;
12 |
--------------------------------------------------------------------------------
/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux-immutable";
2 |
3 | import { reducer as recommendReducer } from "../pages/discover/c-pages/recommend/store";
4 | import { reducer as rankingReducer } from "../pages/discover/c-pages/ranking/store";
5 | import { reducer as songsReducer } from "../pages/discover/c-pages/songs/store";
6 | import { reducer as djradioReducer } from "../pages/discover/c-pages/djradio/store";
7 | import { reducer as albumReducer } from "../pages/discover/c-pages/album/store";
8 | import { reducer as artistReducer } from "../pages/discover/c-pages/artist/store";
9 | import { reducer as playerReducer } from "../pages/player/store";
10 |
11 | export default combineReducers({
12 | recommend: recommendReducer,
13 | ranking: rankingReducer,
14 | songs: songsReducer,
15 | djradio: djradioReducer,
16 | album: albumReducer,
17 | artist: artistReducer,
18 | player: playerReducer
19 | })
20 |
--------------------------------------------------------------------------------
/src/utils/format-utils.js:
--------------------------------------------------------------------------------
1 | export function getSizeImage(imgUrl, size) {
2 | return `${imgUrl}?param=${size}x${size}`;
3 | }
4 |
5 | export function getCount(count) {
6 | if (count < 0) return;
7 | if (count < 10000) {
8 | return count;
9 | } else if (Math.floor(count / 10000) < 10000) {
10 | return Math.floor(count / 1000) / 10 + "万";
11 | } else {
12 | return Math.floor(count / 10000000) / 10 + "亿";
13 | }
14 | }
15 |
16 | export function getPlayUrl(id) {
17 | return `https://music.163.com/song/media/outer/url?id=${id}.mp3`
18 | }
19 |
20 | export function formatDate(time, fmt) {
21 | let date = new Date(time);
22 |
23 | if (/(y+)/.test(fmt)) {
24 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
25 | }
26 | let o = {
27 | 'M+': date.getMonth() + 1,
28 | 'd+': date.getDate(),
29 | 'h+': date.getHours(),
30 | 'm+': date.getMinutes(),
31 | 's+': date.getSeconds()
32 | };
33 | for (let k in o) {
34 | if (new RegExp(`(${k})`).test(fmt)) {
35 | let str = o[k] + '';
36 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
37 | }
38 | }
39 | return fmt;
40 | };
41 |
42 | function padLeftZero(str) {
43 | return ('00' + str).substr(str.length);
44 | };
45 |
46 | export function formatMonthDay(time) {
47 | return formatDate(time, "MM月dd日");
48 | }
49 |
50 | export function formatMinuteSecond(time) {
51 | return formatDate(time, "mm:ss");
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils/handle-data.js:
--------------------------------------------------------------------------------
1 | export function handleSongsCategory(data) {
2 | // 1.获取所有的类别
3 | const category = data.categories;
4 |
5 | // 2.创建类别数据结构
6 | const categoryData = Object.entries(category).map(([key, value]) => {
7 | return {
8 | name: value,
9 | subs: []
10 | }
11 | })
12 |
13 | // 3.将subs添加到对应的类别中
14 | for (let item of data.sub) {
15 | categoryData[item.category].subs.push(item);
16 | }
17 |
18 | return categoryData;
19 | }
20 |
21 | // 获取歌手字母类别
22 | export function generateSingerAlpha() {
23 | var alphabets = ["-1"];
24 | var start = 'A'.charCodeAt(0);
25 | var last = 'Z'.charCodeAt(0);
26 | for (var i = start; i <= last; ++i) {
27 | alphabets.push(String.fromCharCode(i));
28 | }
29 |
30 | alphabets.push("0");
31 |
32 | return alphabets;
33 | }
34 |
35 | export const singerAlphas = generateSingerAlpha();
36 |
--------------------------------------------------------------------------------
/src/utils/lrc-parse.js:
--------------------------------------------------------------------------------
1 | /**
2 | [00:00.000] 作曲 : 许嵩
3 | [00:01.000] 作词 : 许嵩
4 | [00:22.240]天空好想下雨
5 | [00:24.380]我好想住你隔壁
6 | [00:26.810]傻站在你家楼下
7 | [00:29.500]抬起头数乌云
8 | [00:31.160]如果场景里出现一架钢琴
9 | [00:33.640]我会唱歌给你听
10 | [00:35.900]哪怕好多盆水往下淋
11 | [00:41.060]夏天快要过去}
12 | */
13 |
14 | // [00:31.160]如果场景里出现一架钢琴
15 | const parseExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/;
16 |
17 | export function parseLyric(lyricString) {
18 | const lineStrings = lyricString.split("\n");
19 |
20 | const lyrics = [];
21 | for (let line of lineStrings) {
22 | if (line) {
23 | const result = parseExp.exec(line);
24 | if (!result) continue;
25 | const time1 = result[1] * 60 * 1000;
26 | const time2 = result[2] * 1000;
27 | const time3 = result[3].length === 3? result[3]*1: result[3]*10;
28 | const time = time1 + time2 + time3;
29 | const content = line.replace(parseExp, "").trim();
30 | const lineObj = {time, content};
31 | lyrics.push(lineObj);
32 | }
33 | }
34 | return lyrics;
35 | }
--------------------------------------------------------------------------------
/src/utils/ui-helper.js:
--------------------------------------------------------------------------------
1 | export function scrollTo(element, to, duration) {
2 | if (duration <= 0) return;
3 | var difference = to - element.scrollTop;
4 | var perTick = difference / duration * 10;
5 |
6 | setTimeout(function() {
7 | element.scrollTop = element.scrollTop + perTick;
8 | if (element.scrollTop === to) return;
9 | scrollTo(element, to, duration - 10);
10 | }, 10);
11 | }
--------------------------------------------------------------------------------