From f2cf52f9be4c3d5b4808c6795dd6ef1abf03db68 Mon Sep 17 00:00:00 2001 From: "unique.mo" Date: Wed, 6 May 2020 19:28:44 +0800 Subject: [PATCH] feat: add search Result Page --- src/apis/search.ts | 21 +++++- src/apis/types/business.ts | 12 ++-- src/apis/types/search.ts | 20 ++++++ .../Layout/Header/Searcher/index.tsx | 4 ++ src/components/MusicList/index.tsx | 72 +++++++++++++++++++ src/components/MusicList/style.module.css | 28 ++++++++ src/components/Table/index.tsx | 38 ++++++++++ src/components/Table/style.module.css | 27 +++++++ src/constants/routes.ts | 3 + src/hooks/useQuery.ts | 19 +++++ src/pages/App.tsx | 3 + src/pages/Search/MusicList/index.tsx | 19 +++++ src/pages/Search/index.tsx | 68 ++++++++++++++++++ src/pages/Search/style.module.css | 45 ++++++++++++ src/styles/colors.module.css | 1 + src/typings/global.d.ts | 3 + tsconfig.json | 1 + 17 files changed, 376 insertions(+), 8 deletions(-) create mode 100644 src/components/MusicList/index.tsx create mode 100644 src/components/MusicList/style.module.css create mode 100644 src/components/Table/index.tsx create mode 100644 src/components/Table/style.module.css create mode 100644 src/hooks/useQuery.ts create mode 100644 src/pages/Search/MusicList/index.tsx create mode 100644 src/pages/Search/index.tsx create mode 100644 src/pages/Search/style.module.css create mode 100644 src/typings/global.d.ts diff --git a/src/apis/search.ts b/src/apis/search.ts index 87d92d0..41948d8 100644 --- a/src/apis/search.ts +++ b/src/apis/search.ts @@ -1,8 +1,9 @@ import axios from 'helpers/axios' -import { ISearchHot, ISearchSuggestRequest, ISearchSuggestResponse } from './types/search' +import { ISearchHot, ISearchSuggestRequest, ISearchSuggestResponse, ISearchRequest, SEARCH_TYPE } from './types/search' type SearchHotFn = () => Promise type SearchSuggestFn = (params: ISearchSuggestRequest) => Promise +type SearchFn = (params: ISearchRequest) => Promise const searchHot: SearchHotFn = async () => { const response = await axios({ @@ -25,7 +26,23 @@ const searchSuggest: SearchSuggestFn = async ({ keywords }) => { return response.result } +const search: SearchFn = async ({ keywords, type = SEARCH_TYPE.MUSIC, limit = 30, offset = 0 }) => { + const response = await axios({ + method: 'get', + url: '/search', + params: { + keywords, + type, + limit, + offset + } + }) + + return response.result +} + export default { searchHot, - searchSuggest + searchSuggest, + search } diff --git a/src/apis/types/business.ts b/src/apis/types/business.ts index 6b7ba50..d1a7f19 100644 --- a/src/apis/types/business.ts +++ b/src/apis/types/business.ts @@ -42,13 +42,13 @@ export interface IMusic { album: IAlbum, alias: string[], artists: IArtist[], - copyrightId: number, + copyrightId?: number, duration: number, - fee: number, - ftype: number, + fee?: number, + ftype?: number, id: number, - mark: number, - mvid: number, + mark?: number, + mvid?: number, name: string, - status: number + status?: number } diff --git a/src/apis/types/search.ts b/src/apis/types/search.ts index 0dac3e6..cefcc5a 100644 --- a/src/apis/types/search.ts +++ b/src/apis/types/search.ts @@ -26,3 +26,23 @@ export interface ISearchSuggestResponse { mvs: IMV[], songs: IMusic[] } + +// 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频 +export enum SEARCH_TYPE { + MUSIC = 1, + ALBUM = 10, + ARTIST = 100, + SONG_LIST = 1000, + USER = 1002, + MV = 1004, + LYRIC = 1006, + BROADCASTING_STATION = 1009, + VIDEO = 1014 +} + +export interface ISearchRequest { + keywords: string, + type?: SEARCH_TYPE, + limit?: number, + offset?: number +} diff --git a/src/components/Layout/Header/Searcher/index.tsx b/src/components/Layout/Header/Searcher/index.tsx index a35a5df..7b86160 100644 --- a/src/components/Layout/Header/Searcher/index.tsx +++ b/src/components/Layout/Header/Searcher/index.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Icon } from '@blueprintjs/core' +import { useHistory } from 'react-router-dom' import cn from 'classnames' import Words from './Words' @@ -8,11 +9,13 @@ import useAsyncFn from 'hooks/useAsyncFn' import searchApis from 'apis/search' import { setSearchHistory, getSearchHistory } from 'helpers/search' import { debounce } from 'helpers/fn' +import ROUTES from 'constants/routes' import styles from './style.module.css' const { useEffect, useState, useMemo } = React const Searcher = () => { + const history = useHistory() const [showResult, setShowResult] = useState(false) const [keyword, setKeyword] = useState('') const [state, searchHotFn] = useAsyncFn(searchApis.searchHot) @@ -34,6 +37,7 @@ const Searcher = () => { const handleInputKeyPress = async (event: React.KeyboardEvent) => { if (event.key === 'Enter') { + history.push(`${ROUTES.SEARCH}?keyword=${keyword}`) setSearchHistory(keyword) } } diff --git a/src/components/MusicList/index.tsx b/src/components/MusicList/index.tsx new file mode 100644 index 0000000..7e400de --- /dev/null +++ b/src/components/MusicList/index.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import { Icon } from '@blueprintjs/core' + +import Table, { IColumn } from 'components/Table' +import { IMusic, IArtist, IAlbum } from 'apis/types/business' +import { formatTime } from 'helpers/time' +import styles from './style.module.css' + +interface IProps { + data: IMusic[] +} + +const columns: IColumn[] = [ + { + title: '', + key: 'name', + width: '100px', + render: (name: string, record: IMusic, index?: number) => { + return ( +
+ {(index || 0) + 1} + + +
+ ) + } + }, + { + title: '音乐标题', + key: 'name', + width: '45%', + render: (name: string, { alias }: IMusic) => { + return ( + <> +
{name}
+ {alias?.length ?
{alias.join(' ')}
: null} + + ) + } + }, + { + title: '歌手', + key: 'artists', + width: '15%', + render: (artists: IArtist[]) => artists?.map(({ name }) => name).join(' / ') + }, + { + title: '专辑', + key: 'album', + width: '15%', + render: (album: IAlbum) => album?.name + }, + { + title: '时长', + key: 'duration', + width: '10%', + render: (duration: number) => formatTime(duration / 1000) + } +] + +const MusicList: React.FC = ({ data }) => { + return ( +
+ + columns={columns} + data={data} + /> +
+ ) +} + +export default MusicList diff --git a/src/components/MusicList/style.module.css b/src/components/MusicList/style.module.css new file mode 100644 index 0000000..891500d --- /dev/null +++ b/src/components/MusicList/style.module.css @@ -0,0 +1,28 @@ +@value colors: "~styles/colors.module.css"; +@value tipsColor, tipsHoverColor from colors; + +.alias { + margin-top: 10px; + color: tipsColor; +} + +.operations { + display: flex; + justify-content: space-between; + padding-right: 10px; + color: tipsColor; + + .index { + width: 40px; + margin-right: 6px; + text-align: right; + } + + span:nth-child(2), span:nth-child(3) { + cursor: pointer; + + &:hover { + color: tipsHoverColor; + } + } +} diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx new file mode 100644 index 0000000..21de137 --- /dev/null +++ b/src/components/Table/index.tsx @@ -0,0 +1,38 @@ +import React, { ReactElement } from 'react' + +import styles from './style.module.css' + +export interface IColumn { + title?: string, + key: Key, + width?: string, + render: (value: any, record: RecordType, index?: number) => string | ReactElement +} + +interface IProps { + columns: IColumn[], + data: RecordType[] +} + +function Table({ columns, data }: IProps) { + return ( +
+
+ {columns.map(({ title, key, width }, index) => { + return
{title}
+ })} +
+
+ {data?.map((item, index) => { + return
+ {columns.map(({ key, width, render }, idx) => { + return
{render(item[key], item, index)}
+ })} +
+ })} +
+
+ ) +} + +export default Table diff --git a/src/components/Table/style.module.css b/src/components/Table/style.module.css new file mode 100644 index 0000000..0938661 --- /dev/null +++ b/src/components/Table/style.module.css @@ -0,0 +1,27 @@ +@value colors: "~styles/colors.module.css"; +@value tipsHoverColor, bgColor from colors; + +.root { + font-size: 0.9em; +} + +.header { + display: flex; + padding: 10px 0; + color: tipsHoverColor; +} + +.content { + .row { + display: flex; + padding: 10px 0; + } + + .row:nth-child(2n) { + background-color: #fafafa; + } + + .row:hover { + background-color: bgColor; + } +} diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 6f69843..758db64 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -11,6 +11,8 @@ const VIDEOS: string = '/videos' const VIDEO: string = `${VIDEOS}/video` const MV: string = `${VIDEOS}/mv` +const SEARCH: string = '/search' + const DOWNLOAD: string = '/download' const CLOUD: string = '/cloud' const COLLECTION: string = '/collection' @@ -29,6 +31,7 @@ const ROUTES = { VIDEOS, VIDEO, MV, + SEARCH, DOWNLOAD, CLOUD, COLLECTION diff --git a/src/hooks/useQuery.ts b/src/hooks/useQuery.ts new file mode 100644 index 0000000..68f19a3 --- /dev/null +++ b/src/hooks/useQuery.ts @@ -0,0 +1,19 @@ +import { useLocation } from 'react-router-dom' + +const useQuery = () => { + const { search } = useLocation() + const result: IDictionary = {} + + search + .substr(1) + .split('&') + .reduce((prev, curr) => { + const [key, value] = curr.split('=') + prev[key] = decodeURIComponent(value) + return prev + }, result) + + return result +} + +export default useQuery diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 73f3d8a..7efa381 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -4,6 +4,8 @@ import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom' import Layout from 'components/Layout' import Discovery from './Discovery' import Videos from './Videos' +import Search from './Search' + import ROUTES from 'constants/routes' import playMusicReducer, { initialState, @@ -26,6 +28,7 @@ const App = () => { + diff --git a/src/pages/Search/MusicList/index.tsx b/src/pages/Search/MusicList/index.tsx new file mode 100644 index 0000000..057d3a2 --- /dev/null +++ b/src/pages/Search/MusicList/index.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +import MusicListBase from 'components/MusicList' +import { IMusic } from 'apis/types/business' + +interface IProps { + data: IMusic[], + total: number +} + +const MusicList: React.FC = ({ data, total }) => { + return ( +
+ +
+ ) +} + +export default MusicList diff --git a/src/pages/Search/index.tsx b/src/pages/Search/index.tsx new file mode 100644 index 0000000..d964854 --- /dev/null +++ b/src/pages/Search/index.tsx @@ -0,0 +1,68 @@ +import React, { useEffect } from 'react' + +import MusicList from './MusicList' + +import useQuery from 'hooks/useQuery' +import useAsyncFn from 'hooks/useAsyncFn' +import searchApis from 'apis/search' +import styles from './style.module.css' + +interface ITab { + tab: string +} + +const TABS: IDictionary = { + MUSIC: { + tab: '单曲' + }, + ARTIST: { + tab: '歌手' + }, + ALBUM: { + tab: '专辑' + }, + SONG_LIST: { + tab: '歌单' + }, + USER: { + tab: '用户' + } +} + +const Search = () => { + const { keyword } = useQuery() + const [state, searchFn] = useAsyncFn(searchApis.search) + const { value: result } = state + console.log('keyword => ', keyword) + + useEffect(() => { + searchFn({ keywords: keyword }) + }, []) + + console.log('state => ', state) + + return ( +
+
+
+ {keyword} + 找到 1500 首单曲 +
+
+ {Object.keys(TABS).map(key => { + return
{TABS[key].tab}
+ })} +
+
+ +
+ +
+
+ ) +} + +export default Search diff --git a/src/pages/Search/style.module.css b/src/pages/Search/style.module.css new file mode 100644 index 0000000..0c5dd0e --- /dev/null +++ b/src/pages/Search/style.module.css @@ -0,0 +1,45 @@ +@value colors: "~styles/colors.module.css"; +@value borderColor, nameColor, nameHoverColor, tipsHoverColor from colors; + +.root { +} + +.header { + padding: 20px 30px 10px; + border-bottom: 1px solid borderColor; + + .title { + margin-bottom: 20px; + + .keyword { + font-size: 1.6em; + } + + .count { + margin-left: 8px; + font-size: 0.9em; + color: tipsHoverColor; + } + } + + .tabs { + display: flex; + color: nameColor; + + .tab { + margin-right: 30px; + cursor: pointer; + + &:hover { + color: nameHoverColor; + } + } + + .active { + color: black; + } + } +} + +.content { +} diff --git a/src/styles/colors.module.css b/src/styles/colors.module.css index a43f0e4..9707b81 100644 --- a/src/styles/colors.module.css +++ b/src/styles/colors.module.css @@ -6,3 +6,4 @@ @value tipsColor: #bababa; @value tipsHoverColor: #7c7c7c; @value borderColor: #f5f5f5; +@value bgColor: #f2f2f3; diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts new file mode 100644 index 0000000..96ac16b --- /dev/null +++ b/src/typings/global.d.ts @@ -0,0 +1,3 @@ +interface IDictionary { + [key: string]: T +} diff --git a/tsconfig.json b/tsconfig.json index b4bc386..271bba4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "include": ["src"], + "exclude": ["node_modules", "dist"], "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */