├── src ├── boot │ ├── .gitkeep │ └── i18n.ts ├── components │ ├── models.ts │ ├── skeleton │ │ ├── Banner.vue │ │ └── Ytb.vue │ ├── auth │ │ ├── assets │ │ │ ├── 1.png │ │ │ └── 2.png │ │ └── LoginForm.vue │ ├── search │ │ ├── models.ts │ │ ├── Search.vue │ │ ├── HotSearchList.vue │ │ └── CommonSearchList.vue │ ├── loading │ │ └── index.vue │ ├── hooks │ │ └── loading.ts │ ├── recommend │ │ ├── PrivateContentList.vue │ │ ├── RecommendList.vue │ │ ├── PrivateContent.vue │ │ ├── NewSong.vue │ │ └── RecommendItem.vue │ └── carousel-card │ │ └── index.vue ├── i18n │ ├── index.ts │ └── en-US │ │ └── index.ts ├── assets │ ├── daily.png │ └── quasar-logo-vertical.svg ├── api │ ├── index.ts │ ├── search │ │ ├── search.model.d.ts │ │ └── index.ts │ ├── player │ │ ├── index.ts │ │ ├── player.model.d.ts │ │ └── playlist.model.d.ts │ ├── auth │ │ ├── auth.model.d.ts │ │ └── index.ts │ ├── recommend │ │ ├── index.ts │ │ └── recommend.model.d.ts │ └── axios.ts ├── pages │ ├── Download.vue │ ├── Friends.vue │ ├── Live.vue │ ├── LocalMusic.vue │ ├── MyFavorite.vue │ ├── PersonalFM.vue │ ├── home │ │ ├── New.vue │ │ ├── Rank.vue │ │ ├── Singer.vue │ │ ├── Station.vue │ │ ├── SongList.vue │ │ ├── Recommend.vue │ │ └── Index.vue │ ├── search │ │ ├── Song.vue │ │ └── Index.vue │ └── ErrorNotFound.vue ├── css │ ├── app.scss │ └── quasar.variables.scss ├── env.d.ts ├── stores │ ├── store-flag.d.ts │ ├── example-store.ts │ ├── layout.ts │ ├── user.ts │ ├── index.ts │ ├── auth.ts │ └── player.ts ├── shims-vue.d.ts ├── quasar.d.ts ├── router │ ├── routes.ts │ └── index.ts ├── App.vue ├── tests │ ├── utils.ts │ └── fetch.test.ts └── layouts │ ├── SideBar.vue │ ├── RightDrawer.vue │ ├── MainLayout.vue │ └── FooterPlayer.vue ├── .prettierrc ├── .npmrc ├── public ├── favicon.ico └── icons │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ └── favicon-128x128.png ├── .eslintignore ├── vitest.config.ts ├── .editorconfig ├── tsconfig.json ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── README.md ├── postcss.config.js ├── index.html ├── package.json ├── .eslintrc.js └── quasar.config.js /src/boot/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/models.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/skeleton/Banner.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # pnpm-related options 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import enUS from './en-US'; 2 | 3 | export default { 4 | 'en-US': enUS 5 | }; 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/src/assets/daily.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-capacitor 3 | /src-cordova 4 | /.quasar 5 | /node_modules 6 | .eslintrc.js 7 | /src-ssr 8 | -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /src/components/auth/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/src/components/auth/assets/1.png -------------------------------------------------------------------------------- /src/components/auth/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongwa/vue3-quasar-couldMusic-ts/HEAD/src/components/auth/assets/2.png -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './axios'; 3 | export * from './player'; 4 | export * from './search'; 5 | export * from './recommend'; 6 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'jsdom', 6 | globals: true, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/i18n/en-US/index.ts: -------------------------------------------------------------------------------- 1 | // This is just an example, 2 | // so you can safely delete all default props below 3 | 4 | export default { 5 | failed: 'Action failed', 6 | success: 'Action was successful' 7 | }; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@quasar/app-vite/tsconfig-preset", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "api": ["src/api/*"] 7 | }, 8 | "types": ["vitest/globals"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pages/Download.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/Friends.vue: -------------------------------------------------------------------------------- 1 | 2 | 朋友 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/Live.vue: -------------------------------------------------------------------------------- 1 | 2 | 直播 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | .flex-1 { 3 | flex: 1; 4 | } 5 | .radius-sm { 6 | border-radius: 5px; 7 | } 8 | 9 | @for $i from 1 through 10 { 10 | .w-#{$i}0 { 11 | width: 10% * $i !important; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/LocalMusic.vue: -------------------------------------------------------------------------------- 1 | 2 | 本地音乐 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/MyFavorite.vue: -------------------------------------------------------------------------------- 1 | 2 | 我的喜欢 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/PersonalFM.vue: -------------------------------------------------------------------------------- 1 | 2 | 私人FM 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/home/New.vue: -------------------------------------------------------------------------------- 1 | 2 | 新品 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/home/Rank.vue: -------------------------------------------------------------------------------- 1 | 2 | 排行榜 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/home/Singer.vue: -------------------------------------------------------------------------------- 1 | 2 | 歌手 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/home/Station.vue: -------------------------------------------------------------------------------- 1 | 2 | 电台 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/pages/home/SongList.vue: -------------------------------------------------------------------------------- 1 | 2 | 歌单 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | declare namespace NodeJS { 4 | interface ProcessEnv { 5 | NODE_ENV: string; 6 | VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined; 7 | VUE_ROUTER_BASE: string | undefined; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/search/models.ts: -------------------------------------------------------------------------------- 1 | import { ICommonSearch } from 'src/api/search'; 2 | import { InjectionKey } from 'vue'; 3 | 4 | export const searchListKey = Symbol() as InjectionKey; 5 | export const searchOrderKey = Symbol() as InjectionKey; 6 | -------------------------------------------------------------------------------- /src/components/loading/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /src/pages/search/Song.vue: -------------------------------------------------------------------------------- 1 | 2 | 单曲:{{ word }} 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/stores/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/stores/example-store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useCounterStore = defineStore('counter', { 4 | state: () => ({ 5 | counter: 0, 6 | }), 7 | getters: { 8 | doubleCount: (state) => state.counter * 2, 9 | }, 10 | actions: { 11 | increment() { 12 | this.counter++; 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/hooks/loading.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | export function useFetchWithLoading( 4 | fetch: (...rest: R[]) => Promise, 5 | ...r: R[] 6 | ) { 7 | const loading = ref(true); 8 | const data = ref(); 9 | fetch(...r).then((res) => { 10 | data.value = res; 11 | loading.value = false; 12 | }); 13 | return { loading, data }; 14 | } 15 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /// 4 | /// 5 | 6 | // Mocks all files ending in `.vue` showing them as plain Vue instances 7 | declare module '*.vue' { 8 | import type { DefineComponent } from 'vue'; 9 | const component: DefineComponent<{}, {}, any>; 10 | export default component; 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "editorconfig.editorconfig", 6 | "vue.volar", 7 | "wayou.vscode-todo-highlight" 8 | ], 9 | "unwantedRecommendations": [ 10 | "octref.vetur", 11 | "hookyqr.beautify", 12 | "dbaeumer.jshint", 13 | "ms-vscode.vscode-typescript-tslint-plugin" 14 | ] 15 | } -------------------------------------------------------------------------------- /src/api/search/search.model.d.ts: -------------------------------------------------------------------------------- 1 | declare interface hotSearchItem { 2 | alg: string; 3 | content: string; 4 | iconType: number; 5 | iconUrl: string; 6 | score: number; 7 | searchWord: string; 8 | source: number; 9 | url: string; 10 | } 11 | 12 | declare interface ICommonSearch { 13 | albums?: IAlbum[]; 14 | songs?: ISong[]; 15 | artists?: IArtist[]; 16 | playlists?: any[]; 17 | order?: string[]; 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.bracketPairColorization.enabled": true, 3 | "editor.guides.bracketPairs": true, 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.codeActionsOnSave": [ 7 | "source.fixAll.eslint" 8 | ], 9 | "eslint.validate": [ 10 | "javascript", 11 | "javascriptreact", 12 | "typescript", 13 | "vue" 14 | ], 15 | "typescript.tsdk": "node_modules/typescript/lib" 16 | } -------------------------------------------------------------------------------- /src/components/skeleton/Ytb.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/quasar.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package 4 | // Removing this would break `quasar/wrappers` imports as those typings are declared 5 | // into `@quasar/app-vite` 6 | // As a side effect, since `@quasar/app-vite` reference `quasar` to augment it, 7 | // this declaration also apply `quasar` own 8 | // augmentations (eg. adds `$q` into Vue component context) 9 | /// 10 | -------------------------------------------------------------------------------- /src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | /** 根据 pages 目录结构自动生成的路由 */ 3 | import generateRoutes from '~pages'; 4 | 5 | const routes: RouteRecordRaw[] = [ 6 | { 7 | path: '/', 8 | redirect: '/home', 9 | component: () => import('layouts/MainLayout.vue'), 10 | children: generateRoutes, 11 | }, 12 | 13 | // Always leave this as last one, 14 | // but you can also remove it 15 | { 16 | path: '/:catchAll(.*)*', 17 | component: () => import('pages/ErrorNotFound.vue'), 18 | }, 19 | ]; 20 | 21 | export default routes; 22 | -------------------------------------------------------------------------------- /src/stores/layout.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useLayoutStore = defineStore('layout', { 4 | state: () => ({ 5 | rightDrawerOpen: false, 6 | leftDrawerOpen: true, 7 | loginFormSwitch: false, 8 | }), 9 | actions: { 10 | toggleLoginForm(data: boolean) { 11 | this.loginFormSwitch = data; 12 | }, 13 | togglerightDrawerOpen(this) { 14 | this.rightDrawerOpen = !this.rightDrawerOpen; 15 | }, 16 | toggleleftDrawerOpen(this) { 17 | this.leftDrawerOpen = !this.leftDrawerOpen; 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/pages/ErrorNotFound.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 6 | 7 | 8 | 9 | Oops. Nothing here... 10 | 11 | 12 | 21 | 22 | 23 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | 9 | # Cordova related directories and files 10 | /src-cordova/node_modules 11 | /src-cordova/platforms 12 | /src-cordova/plugins 13 | /src-cordova/www 14 | 15 | # Capacitor related directories and files 16 | /src-capacitor/www 17 | /src-capacitor/node_modules 18 | 19 | # BEX related directories and files 20 | /src-bex/www 21 | /src-bex/js/core 22 | 23 | # Log files 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Editor directories and files 29 | .idea 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quasar Music APP (quasar-music) 2 | 3 | Quasar Music APP 4 | 5 | ## Install the dependencies 6 | ```bash 7 | yarn 8 | # or 9 | npm install 10 | ``` 11 | 12 | ### Start the app in development mode (hot-code reloading, error reporting, etc.) 13 | ```bash 14 | quasar dev 15 | ``` 16 | 17 | 18 | ### Lint the files 19 | ```bash 20 | yarn lint 21 | # or 22 | npm run lint 23 | ``` 24 | 25 | 26 | ### Format the files 27 | ```bash 28 | yarn format 29 | # or 30 | npm run format 31 | ``` 32 | 33 | 34 | 35 | ### Build the app for production 36 | ```bash 37 | quasar build 38 | ``` 39 | 40 | ### Customize the configuration 41 | See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js). 42 | -------------------------------------------------------------------------------- /src/tests/utils.ts: -------------------------------------------------------------------------------- 1 | export const retry = ( 2 | assertion: (...args: any[]) => any, 3 | { interval = 1, timeout = 100 } = {} 4 | ) => { 5 | return new Promise((resolve, reject) => { 6 | const startTime = Date.now(); 7 | 8 | const tryAgain = () => { 9 | setTimeout(() => { 10 | try { 11 | resolve(assertion()); 12 | } catch (err) { 13 | Date.now() - startTime > timeout ? reject(err) : tryAgain(); 14 | } 15 | }, interval); 16 | try { 17 | // If useFakeTimers hasn't been called, this will throw 18 | vitest.advanceTimersByTime(interval); 19 | } catch (e) { 20 | /* Expected to throw */ 21 | } 22 | }; 23 | 24 | tryAgain(); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/recommend/PrivateContentList.vue: -------------------------------------------------------------------------------- 1 | 2 | 独家放送 > 3 | 4 | 5 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /src/api/player/index.ts: -------------------------------------------------------------------------------- 1 | import { api } from 'src/api/axios'; 2 | 3 | export enum PlayerApiUrl { 4 | playlist = '/playlist/detail', //传入歌单id获取歌单信息 5 | songDetail = '/song/detail', //传入歌曲id可以获取歌曲信息,可同时传入多个id用逗号分隔 6 | playUrl = '/song/url', //传入歌曲id获取播放连接 7 | } 8 | 9 | export const getPlaylistInfoById = async (id: number) => { 10 | const res = await api.get(`${PlayerApiUrl.playlist}?id=${id}`); 11 | return res.data.playlist as IPlaylistDetail; 12 | }; 13 | export const getPlaylist = async (ids: string) => { 14 | const res = await api.get(`${PlayerApiUrl.songDetail}?ids=${ids}`); 15 | return res.data.songs as ISongInfo[]; 16 | }; 17 | 18 | export const getPlayUrl = async (id: number): Promise => { 19 | const res = await api.get(`${PlayerApiUrl.playUrl}?id=${id}`); 20 | return res.data.data[0]; 21 | }; 22 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/michael-ciniawsky/postcss-load-config 3 | 4 | module.exports = { 5 | plugins: [ 6 | // https://github.com/postcss/autoprefixer 7 | require('autoprefixer')({ 8 | overrideBrowserslist: [ 9 | 'last 4 Chrome versions', 10 | 'last 4 Firefox versions', 11 | 'last 4 Edge versions', 12 | 'last 4 Safari versions', 13 | 'last 4 Android versions', 14 | 'last 4 ChromeAndroid versions', 15 | 'last 4 FirefoxAndroid versions', 16 | 'last 4 iOS versions' 17 | ] 18 | }) 19 | 20 | // https://github.com/elchininet/postcss-rtlcss 21 | // If you want to support RTL css, then 22 | // 1. yarn/npm install postcss-rtlcss 23 | // 2. optionally set quasar.config.js > framework > lang to an RTL language 24 | // 3. uncomment the following line: 25 | // require('postcss-rtlcss') 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | const storeID = 'user'; 4 | 5 | interface IUser { 6 | loggedIn: boolean; 7 | } 8 | 9 | const User: IUser = { 10 | loggedIn: false, 11 | }; 12 | 13 | export { IUser }; 14 | 15 | export const useUserStore = defineStore({ 16 | id: storeID, 17 | 18 | state: (): IUser => 19 | JSON.parse(localStorage.getItem(storeID) ?? JSON.stringify(User)) as IUser, 20 | 21 | actions: { 22 | login(username: string, password: string): boolean { 23 | if (username === 'admin' && password === 'admin') { 24 | this.loggedIn = true; 25 | } 26 | 27 | return this.loggedIn; 28 | }, 29 | logout(): boolean { 30 | this.loggedIn = false; 31 | return true; 32 | }, 33 | }, 34 | getters: { 35 | isLoggedIn(): boolean { 36 | return this.loggedIn; 37 | }, 38 | }, 39 | }); 40 | 41 | export type UserStore = ReturnType; 42 | -------------------------------------------------------------------------------- /src/pages/home/Recommend.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 36 | -------------------------------------------------------------------------------- /src/api/auth/auth.model.d.ts: -------------------------------------------------------------------------------- 1 | declare interface IUserInfo { 2 | userId: number; 3 | userType: number; 4 | nickname: string; 5 | avatarImgId: number; 6 | avatarUrl: string; 7 | backgroundImgId: number; 8 | backgroundUrl: string; 9 | signature?: any; 10 | createTime: number; 11 | userName: string; 12 | accountType: number; 13 | shortUserName: string; 14 | birthday: number; 15 | authority: number; 16 | gender: number; 17 | accountStatus: number; 18 | province: number; 19 | city: number; 20 | authStatus: number; 21 | description?: any; 22 | detailDescription?: any; 23 | defaultAvatar: boolean; 24 | expertTags?: any; 25 | experts?: any; 26 | djStatus: number; 27 | locationStatus: number; 28 | vipType: number; 29 | followed: boolean; 30 | mutual: boolean; 31 | authenticated: boolean; 32 | lastLoginTime: number; 33 | lastLoginIP: string; 34 | remarkName?: any; 35 | viptypeVersion: number; 36 | authenticationTypes: number; 37 | avatarDetail?: any; 38 | anchor: boolean; 39 | } 40 | -------------------------------------------------------------------------------- /src/api/search/index.ts: -------------------------------------------------------------------------------- 1 | import { api } from '../axios'; 2 | 3 | enum SearchApi { 4 | keywordsSearch = '/cloudsearch', 5 | } 6 | 7 | /** 根据关键字搜索 8 | * @param limit 表示数量,默认30 9 | * @param offset 偏移量,用于分页,默认为0 10 | * @param type 搜索类型 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单 11 | * 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频, 1018:综合, 2000:声音 12 | */ 13 | export async function searchBykeywords( 14 | keyword: string, 15 | limit = 30, 16 | offset = 0, 17 | type = 1 18 | ) { 19 | const res = await api.get( 20 | `${SearchApi.keywordsSearch}?keywords=${keyword}&offset=${offset}&limit=${limit}&type=${type}` 21 | ); 22 | return res.data; 23 | } 24 | 25 | /** 根据关键词获取列表,可用于首页搜索框卡片上的搜索 */ 26 | export async function getSearchList(keywords: string) { 27 | const res = await api.get(`/search/suggest?keywords=${keywords}`); 28 | return res.data.result; 29 | } 30 | 31 | /** 获取热搜,可用于首页搜索框的默认搜索 */ 32 | export async function getHotSearch(): Promise { 33 | const res = await api.get('/search/hot/detail'); 34 | return res.data.data; 35 | } 36 | -------------------------------------------------------------------------------- /src/boot/i18n.ts: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers'; 2 | import { createI18n } from 'vue-i18n'; 3 | 4 | import messages from 'src/i18n'; 5 | 6 | export type MessageLanguages = keyof typeof messages; 7 | // Type-define 'en-US' as the master schema for the resource 8 | export type MessageSchema = typeof messages["en-US"]; 9 | 10 | // See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition 11 | /* eslint-disable @typescript-eslint/no-empty-interface */ 12 | declare module "vue-i18n" { 13 | // define the locale messages schema 14 | export interface DefineLocaleMessage extends MessageSchema {} 15 | 16 | // define the datetime format schema 17 | export interface DefineDateTimeFormat {} 18 | 19 | // define the number format schema 20 | export interface DefineNumberFormat {} 21 | } 22 | /* eslint-enable @typescript-eslint/no-empty-interface */ 23 | 24 | export default boot(({ app }) => { 25 | const i18n = createI18n({ 26 | locale: 'en-US', 27 | legacy: false, 28 | messages, 29 | }); 30 | 31 | // Set i18n instance on app 32 | app.use(i18n); 33 | }); 34 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers'; 2 | import { 3 | createMemoryHistory, 4 | createRouter, 5 | createWebHashHistory, 6 | createWebHistory, 7 | } from 'vue-router'; 8 | 9 | import routes from './routes'; 10 | 11 | /* 12 | * If not building with SSR mode, you can 13 | * directly export the Router instantiation; 14 | * 15 | * The function below can be async too; either use 16 | * async/await or return a Promise which resolves 17 | * with the Router instance. 18 | */ 19 | 20 | export default route(function (/* { store, ssrContext } */) { 21 | const createHistory = process.env.SERVER 22 | ? createMemoryHistory 23 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); 24 | 25 | const Router = createRouter({ 26 | scrollBehavior: () => ({ left: 0, top: 0 }), 27 | routes, 28 | 29 | // Leave this as is and make changes in quasar.conf.js instead! 30 | // quasar.conf.js -> build -> vueRouterMode 31 | // quasar.conf.js -> build -> publicPath 32 | history: createHistory(process.env.VUE_ROUTER_BASE), 33 | }); 34 | 35 | return Router; 36 | }); 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/tests/fetch.test.ts: -------------------------------------------------------------------------------- 1 | import { useGet } from '../api/axios'; 2 | import { retry } from './utils'; 3 | const testAPis = { 4 | search: '/search', 5 | }; 6 | 7 | describe('useFetch', () => { 8 | it('without await and then', async () => { 9 | const { response } = useGet(testAPis.search, { 10 | keywords: '海阔天空', 11 | }); 12 | await retry( 13 | () => { 14 | expect(response.value).not.toBeUndefined(); 15 | expect(response.value?.status).lessThanOrEqual(400); 16 | }, 17 | { 18 | timeout: 3 * 1000, 19 | } 20 | ); 21 | }); 22 | 23 | it('it whit await', async () => { 24 | const { response } = await useGet(testAPis.search, { 25 | keywords: '海阔天空', 26 | }); 27 | expect(response.value).not.toBeUndefined(); 28 | expect(response.value?.status).lessThanOrEqual(400); 29 | }); 30 | 31 | it('it whit then', () => { 32 | useGet(testAPis.search, { 33 | keywords: '海阔天空', 34 | }).then((r) => { 35 | expect(r.response.value).not.toBeUndefined(); 36 | expect(r.response.value?.status).lessThanOrEqual(400); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { store } from 'quasar/wrappers'; 2 | import { createPinia } from 'pinia'; 3 | import { Router } from 'vue-router'; 4 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; 5 | 6 | export * from './auth'; 7 | export * from './layout'; 8 | export * from './user'; 9 | export * from './player'; 10 | 11 | /* 12 | * When adding new properties to stores, you should also 13 | * extend the `PiniaCustomProperties` interface. 14 | * @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties 15 | */ 16 | declare module 'pinia' { 17 | export interface PiniaCustomProperties { 18 | readonly router: Router; 19 | } 20 | } 21 | 22 | /* 23 | * If not building with SSR mode, you can 24 | * directly export the Store instantiation; 25 | * 26 | * The function below can be async too; either use 27 | * async/await or return a Promise which resolves 28 | * with the Store instance. 29 | */ 30 | 31 | export default store((/* { ssrContext } */) => { 32 | const pinia = createPinia().use(piniaPluginPersistedstate); 33 | 34 | // You can add Pinia plugins here 35 | // pinia.use(SomePiniaPlugin) 36 | 37 | return pinia; 38 | }); 39 | -------------------------------------------------------------------------------- /src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary: #ec4141; 16 | $secondary: #26a69a; 17 | $accent: #9c27b0; 18 | 19 | $dark: #1d1d1d; 20 | 21 | $positive: #21ba45; 22 | $negative: #c10015; 23 | $info: #31ccec; 24 | $warning: #f2c037; 25 | 26 | // 统一的主题色与字体颜色 27 | $primary-color: #ec4141; 28 | $text-color: #444; 29 | 30 | // 头部字体颜色 31 | $header-text-color: #f2f3f4; 32 | // 左右按钮背景色 33 | $header-history-background: #d83b3b; 34 | // input框颜色 35 | $header-input-background: #e13e3e; 36 | $header-input-placeholder-color: #e66262; 37 | 38 | $header-height: 60px; 39 | $player-height: 70px; 40 | $sidebar-width: 200px; 41 | $tab-padding_horizon: 2.5%; 42 | $primary-color: #ec4141; 43 | -------------------------------------------------------------------------------- /src/pages/home/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 35 | 36 | 46 | -------------------------------------------------------------------------------- /src/api/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { api } from '../axios'; 2 | 3 | enum ApiUrl { 4 | getKey = '/login/qr/key', //说明: 调用此接口可生成一个key 5 | createQR = '/login/qr/create', //说明: 调用此接口传入上一个接口生成的key可生成二维码图片的base64和二维码信息,可使用base64展示图片,或者使用二维码信息内容自行使用第三方二维码生产库渲染二维码 6 | check = '/login/qr/check', 7 | getUerInfo = '/user/account', 8 | } 9 | 10 | //调用此接口可生成一个用于二维码登录的key 11 | export const getQRKey = async (): Promise => { 12 | const res = await api.get(`${ApiUrl.getKey}?timerstamp=${Date.now()}`); 13 | return res.data.data.unikey; 14 | }; 15 | 16 | //获取登录二维码,调用之前得先获得key 17 | export const createQRbase64 = async (key: string) => { 18 | const res = await api.get( 19 | `${ApiUrl.createQR}?key=${key}&qrimg=true&timerstamp=${Date.now()}` 20 | ); 21 | return res.data.data.qrimg; 22 | }; 23 | 24 | /** 25 | * 轮询此接口可获取二维码扫码状态 26 | * 800为二维码过期 27 | * 801为等待扫码 28 | * 802为待确认 29 | * 803为授权登录成功(803状态码下会返回cookies和用户名头像) 30 | **/ 31 | export const checkPrLogin = async (key: string) => { 32 | return await ( 33 | await api.get(`${ApiUrl.check}?key=${key}&timerstamp=${Date.now()}`) 34 | ).data; 35 | }; 36 | 37 | //获取到cookie后可以通过此接口可获取用户详细数据 38 | export const getUserInfo = async (): Promise<{ 39 | profile: IUserInfo; 40 | }> => { 41 | const res = await api.get(`${ApiUrl.getUerInfo}?timerstamp=${Date.now()}`); 42 | return res.data; 43 | }; 44 | -------------------------------------------------------------------------------- /src/stores/auth.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutStore } from './layout'; 2 | import { defineStore } from 'pinia'; 3 | import { Cookies } from 'quasar'; 4 | import { getUserInfo } from 'src/api/auth'; 5 | import { IUserInfo } from 'src/api/auth/auth.model'; 6 | 7 | export interface IAuthState { 8 | isLogin: boolean; 9 | userInfo: IUserInfo; 10 | } 11 | 12 | export const useAuthStore = defineStore('auth', { 13 | state: () => ({ 14 | isLogin: false, 15 | userInfo: { 16 | nickname: '', 17 | avatarUrl: '', 18 | }, 19 | }), 20 | actions: { 21 | async setUserInfo() { 22 | const layout = useLayoutStore(); 23 | const userInfo = await getUserInfo().catch((err) => { 24 | //获取用户信息失败,可能是cookie过期了,清除cookie,等待用户重新登录 25 | console.log(err); 26 | Cookies.remove('HASCOOKIE'); 27 | }); 28 | console.log('获取用户信息', userInfo); 29 | 30 | //将用户信息存入store中 31 | if (userInfo) this.userInfo = userInfo.profile; 32 | // 在cooki 中存入一条记录表示有cookie存在(由于服务器给的cookie设置了httpOnly,不可访问,所以自己存个标志) 33 | Cookies.set('HASCOOKIE', 'true', { 34 | expires: 20, //有效期20天 35 | }); 36 | //登录成功,关闭登录框 37 | this.isLogin = true; 38 | layout.toggleLoginForm(false); 39 | }, 40 | }, 41 | persist: true, 42 | }); 43 | 44 | export type AuthStore = ReturnType; 45 | -------------------------------------------------------------------------------- /src/pages/search/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 搜索 {{ word }} 4 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /src/components/recommend/RecommendList.vue: -------------------------------------------------------------------------------- 1 | 2 | 推荐歌单 > 3 | 4 | 10 | 11 | 12 | 13 | 45 | -------------------------------------------------------------------------------- /src/components/recommend/PrivateContent.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {{ item.name }} 17 | 18 | 19 | 20 | 35 | 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-music", 3 | "version": "0.0.1", 4 | "description": "Quasar Music APP", 5 | "productName": "Quasar Music APP", 6 | "author": "dongwa <783137663@qq.com>", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --ext .js,.ts,.vue ./", 10 | "format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore", 11 | "test": "echo \"No test specified\" && exit 0" 12 | }, 13 | "dependencies": { 14 | "@quasar/extras": "^1.0.0", 15 | "axios": "^0.21.1", 16 | "pinia": "^2.0.11", 17 | "pinia-plugin-persistedstate": "^3.0.2", 18 | "quasar": "^2.6.0", 19 | "swiper": "^8.0.6", 20 | "vite-plugin-pages": "^0.28.0", 21 | "vue": "^3.0.0", 22 | "vue-i18n": "^9.2.2", 23 | "vue-router": "^4.0.0" 24 | }, 25 | "devDependencies": { 26 | "@intlify/vite-plugin-vue-i18n": "^3.3.1", 27 | "@quasar/app-vite": "^1.0.0", 28 | "@types/node": "^12.20.21", 29 | "@typescript-eslint/eslint-plugin": "^5.10.0", 30 | "@typescript-eslint/parser": "^5.10.0", 31 | "autoprefixer": "^10.4.2", 32 | "eslint": "^8.10.0", 33 | "eslint-config-prettier": "^8.1.0", 34 | "eslint-plugin-vue": "^9.0.0", 35 | "jsdom": "^21.1.0", 36 | "prettier": "^2.5.1", 37 | "typescript": "^4.5.4", 38 | "vitest": "^0.28.3" 39 | }, 40 | "engines": { 41 | "node": "^18 || ^16 || ^14.19", 42 | "npm": ">= 6.13.4", 43 | "yarn": ">= 1.21.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/search/Search.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 47 | 48 | 62 | -------------------------------------------------------------------------------- /src/api/recommend/index.ts: -------------------------------------------------------------------------------- 1 | import { api } from '../axios'; 2 | 3 | enum RecommendApiUrl { 4 | getBanners = '/banner', 5 | recommendPlayList = '/personalized', 6 | privateContent = '/personalized/privatecontent', 7 | privatecontentList = '/personalized/privatecontent/list', 8 | newSongs = '/personalized/newsong', 9 | } 10 | /** 11 | * 返回首页banner数据 12 | * @param type 资源类型,对应以下类型,默认为 0 即PC 13 | * 0: pc 14 | * 1: android 15 | * 2: iphone 16 | * 3: ipad 17 | */ 18 | export const getBanners = async (type = 0): Promise => { 19 | const res = await api.get(`${RecommendApiUrl.getBanners}?type=${type}`); 20 | return res.data.banners; 21 | }; 22 | 23 | export const getRecommendPlayList = async ( 24 | limit = 9 25 | ): Promise => { 26 | const res = await api.get( 27 | `${RecommendApiUrl.recommendPlayList}?limit=${limit}` 28 | ); 29 | return res.data.result; 30 | }; 31 | 32 | //返回入口列表的三个独家内容 33 | export const getPrivateContent = async (): Promise => { 34 | const res = await api.get(`${RecommendApiUrl.privateContent}`); 35 | return res.data.result; 36 | }; 37 | /** 38 | * 返回独家内容列表 39 | * @param limit 每次请求的数量 40 | * @param offset 偏移量,用于分页,相当于页数 41 | */ 42 | export const getPrivateContentList = async ( 43 | limit: number, 44 | offset: number 45 | ): Promise => { 46 | const res = await api.get( 47 | `${RecommendApiUrl.privatecontentList}?limit=${limit}&offset=${offset}` 48 | ); 49 | return res.data; 50 | }; 51 | //返回新歌推荐 52 | export const getNewSongs = async (limit: number): Promise => { 53 | const res = await api(`${RecommendApiUrl.newSongs}?limit=${limit}`); 54 | return res.data.result; 55 | }; 56 | -------------------------------------------------------------------------------- /src/api/recommend/recommend.model.d.ts: -------------------------------------------------------------------------------- 1 | //歌单信息(用户首页推荐歌单) 2 | declare interface IPlayListInfo { 3 | id: number; 4 | type: number; 5 | name: string; 6 | copywriter: string; 7 | picUrl: string; 8 | canDislike: boolean; 9 | trackNumberUpdateTime: string; 10 | playCount: number; 11 | trackCount: number; 12 | highQuality: boolean; 13 | alg: string; 14 | } 15 | //首页banner信息 16 | declare interface IBannerInfo { 17 | // adDispatchJson: null; 18 | // adLocation: null; 19 | // adSource: null; 20 | // adid: null; 21 | // event: null; 22 | // extMonitor: null; 23 | // extMonitorInfo: null; 24 | // monitorBlackList: null; 25 | // monitorClick: null; 26 | // monitorClickList: null; 27 | // monitorImpress: null; 28 | // monitorImpressList: null; 29 | // monitorType: null; 30 | // program: null; 31 | // song: null; 32 | // url: null; 33 | // video: null; 34 | encodeId: string; 35 | exclusive: boolean; 36 | imageUrl: string; 37 | scm: string; 38 | targetId: number; 39 | targetType: number; 40 | titleColor: string; 41 | typeTitle: string; 42 | } 43 | 44 | //独家内容信息(用于首页独家放送) 45 | declare interface IPrivateConetnt { 46 | id: number; 47 | url: string; 48 | picUrl: string; 49 | sPicUrl: string; 50 | type: number; 51 | copywriter: string; 52 | name: string; 53 | alg: string; 54 | } 55 | 56 | declare interface IPrivateConetntList { 57 | result: IPrivateConetnt[]; 58 | more: boolean; 59 | offset: number; 60 | } 61 | 62 | //用于首页最新音乐 63 | declare interface INewSongsInfo { 64 | id: number; 65 | type: number; 66 | name: string; 67 | copywriter?: any; 68 | picUrl: string; 69 | canDislike: boolean; 70 | trackNumberUpdateTime?: any; 71 | song: ISong; 72 | alg: string; 73 | } 74 | -------------------------------------------------------------------------------- /src/stores/player.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { ISongInfo } from 'src/api/player/playlist.model'; 3 | 4 | //播放方式 5 | export enum PlayMode { 6 | circulation = 0, //循环 7 | Sequence = 1, // 顺序 8 | random = 2, //随机 9 | } 10 | export interface IPlayerState { 11 | playMode: PlayMode; 12 | index: number; 13 | curentPlaySong: ISongInfo | null; //当前播放的歌曲 14 | playlist: ISongInfo[]; //播放列表 15 | history: number[]; 16 | } 17 | 18 | export const usePlayerStore = defineStore('player', { 19 | state: (): IPlayerState => ({ 20 | playMode: 1, 21 | index: 0, 22 | curentPlaySong: null, 23 | playlist: [], 24 | history: [], 25 | }), 26 | actions: { 27 | setPlaylist(data: ISongInfo[]) { 28 | this.playlist = data; 29 | this.history = []; 30 | }, 31 | changePlayMode() { 32 | this.playMode = (this.playMode + 1) % 3; 33 | }, 34 | setCurentPlaySong(data: ISongInfo, index: number) { 35 | this.curentPlaySong = data; 36 | this.index = index; 37 | this.history.push(index); 38 | }, 39 | 40 | /** 上一首 */ 41 | setPreviousSong() { 42 | this.history.pop(); 43 | let index = this.history.pop(); 44 | /** 若历史列表中没有上一曲了,则随机播放 */ 45 | if (!index) { 46 | const len = this.playlist.length; 47 | index = Math.floor(Math.random() * (len + 1)); 48 | } 49 | this.curentPlaySong = this.playlist[index]; 50 | }, 51 | /** 下一首 */ 52 | setNextSong() { 53 | if (this.playMode > 1) 54 | this.index = Math.floor(Math.random() * (this.playlist.length + 1)); 55 | else this.index += this.playMode; 56 | this.curentPlaySong = this.playlist[this.index]; 57 | this.history.push(this.index); 58 | }, 59 | }, 60 | persist: true, 61 | }); 62 | 63 | export type PlayerStore = ReturnType; 64 | -------------------------------------------------------------------------------- /src/components/recommend/NewSong.vue: -------------------------------------------------------------------------------- 1 | 2 | 最新音乐 > 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {{ item.name }} 24 | 25 | 26 | 27 | {{ getArtists(item.song.artists) }} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 61 | 73 | -------------------------------------------------------------------------------- /src/components/search/HotSearchList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 热搜榜 4 | 5 | 12 | 13 | {{ index + 1 }} 14 | 15 | 16 | 17 | {{ item.searchWord }} 18 | 26 | {{ item.score }} 27 | 28 | {{ item.content }} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 43 | 86 | -------------------------------------------------------------------------------- /src/components/auth/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 扫码登陆 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 86 | -------------------------------------------------------------------------------- /src/components/carousel-card/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 74 | 75 | 102 | -------------------------------------------------------------------------------- /src/layouts/SideBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 发现音乐 5 | 6 | 7 | 朋友 8 | 9 | 10 | 直播 11 | 12 | 13 | 私人FM 14 | 15 | 16 | 我的音乐 17 | 18 | 19 | 20 | 21 | 22 | 本地音乐 23 | 24 | 25 | 26 | 27 | 28 | 29 | 下载管理 30 | 31 | 32 | 33 | 34 | 创建的歌单 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 我喜欢的音乐 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 67 | 68 | 92 | -------------------------------------------------------------------------------- /src/components/search/CommonSearchList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 搜' 5 | {{ searchVal }} 6 | '相关的结果 7 | 8 | 9 | 10 | 11 | {{ getCategory(category) }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 75 | 97 | -------------------------------------------------------------------------------- /src/api/axios.ts: -------------------------------------------------------------------------------- 1 | import { Ref, ref } from 'vue'; 2 | import { Notify } from 'quasar'; 3 | import axios, { 4 | AxiosPromise, 5 | AxiosRequestConfig, 6 | AxiosResponse, 7 | Canceler, 8 | CancelTokenSource, 9 | } from 'axios'; 10 | const CancelToken = axios.CancelToken; 11 | 12 | const api = axios.create({ 13 | baseURL: 'https://music.api.quasar-cn.cn', 14 | withCredentials: true, 15 | }); 16 | 17 | //响应拦截 18 | api.interceptors.response.use( 19 | (response) => { 20 | // console.log(response); 21 | if (response.status < 400) { 22 | return Promise.resolve(response); 23 | } else { 24 | Notify.create({ 25 | color: 'red', 26 | message: response.data, 27 | }); 28 | return Promise.reject(response); 29 | } 30 | }, 31 | (err) => { 32 | Notify.create({ 33 | color: 'red', 34 | message: err.toString(), 35 | }); 36 | return Promise.reject(err); 37 | } 38 | ); 39 | 40 | export interface IFetchControler { 41 | loading: boolean; 42 | data: D; 43 | source: CancelTokenSource; 44 | } 45 | 46 | export interface FetchReturn { 47 | cancel: Canceler; 48 | loading: Ref; 49 | data: Ref; 50 | source: Ref; 51 | response: Ref | undefined>; 52 | } 53 | 54 | export type FetchControler = ReturnType>; 55 | 56 | export type FetchConfig = AxiosRequestConfig & { 57 | controller?: FetchControler; 58 | }; 59 | 60 | export function useFetchControler() { 61 | return { 62 | loading: ref(true), 63 | data: ref(), 64 | source: ref(CancelToken.source()), 65 | }; 66 | } 67 | 68 | export function useGet(url: string, params = {}, opt: FetchConfig = {}) { 69 | return useFetch(url, { 70 | method: 'GET', 71 | params, 72 | ...opt, 73 | }); 74 | } 75 | 76 | export function usePost(url: string, data = {}, opt: FetchConfig = {}) { 77 | return useFetch(url, { 78 | method: 'POST', 79 | data, 80 | ...opt, 81 | }); 82 | } 83 | 84 | export function useFetch( 85 | url: string, 86 | opt: FetchConfig = {} 87 | ): FetchReturn & Promise> { 88 | const response = ref>(); 89 | const timerstamp = Date.now().toString(); 90 | const controller = opt?.controller || useFetchControler(); 91 | opt.cancelToken = controller.source.value.token; 92 | if (opt.params) opt.params.timerstamp = timerstamp; 93 | if (opt.data) opt.data.timerstamp = timerstamp; 94 | 95 | const p = api(url, opt) as AxiosPromise; 96 | 97 | const result = { 98 | response, 99 | ...controller, 100 | cancel: controller.source.value.cancel, 101 | }; 102 | 103 | const rp = p.then((r) => { 104 | controller.loading.value = false; 105 | controller.data.value = r.data; 106 | response.value = r; 107 | return result; 108 | }); 109 | 110 | p.catch((e) => { 111 | if (axios.isCancel(e)) { 112 | console.log('Request canceled', e.message); 113 | Notify.create({ 114 | color: 'yellow', 115 | message: '已取消', 116 | }); 117 | } else { 118 | throw e; 119 | } 120 | }); 121 | 122 | return Object.assign(rp, result); 123 | } 124 | 125 | export { axios, api }; 126 | -------------------------------------------------------------------------------- /src/layouts/RightDrawer.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 32 | 33 | {{ item.name }} 34 | 35 | 36 | 37 | 38 | {{ formatArtistsName(item.ar) }} 42 | 43 | 44 | 45 | {{ durationTime(item.dt) }} 46 | 47 | 48 | 49 | 50 | 51 | AlarmsLorem ipsum dolor sit amet consectetur adipisicing elit. 52 | 53 | 54 | 55 | 56 | 86 | 87 | 98 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | // https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser 8 | // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working 9 | // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted 10 | parserOptions: { 11 | parser: require.resolve('@typescript-eslint/parser'), 12 | extraFileExtensions: ['.vue'], 13 | }, 14 | 15 | env: { 16 | browser: true, 17 | es2021: true, 18 | node: true, 19 | 'vue/setup-compiler-macros': true, 20 | }, 21 | 22 | // Rules order is important, please avoid shuffling them 23 | extends: [ 24 | // Base ESLint recommended rules 25 | // 'eslint:recommended', 26 | 27 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage 28 | // ESLint typescript rules 29 | 'plugin:@typescript-eslint/recommended', 30 | 31 | // Uncomment any of the lines below to choose desired strictness, 32 | // but leave only one uncommented! 33 | // See https://eslint.vuejs.org/rules/#available-rules 34 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 35 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 36 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 37 | 38 | // https://github.com/prettier/eslint-config-prettier#installation 39 | // usage with Prettier, provided by 'eslint-config-prettier'. 40 | 'prettier', 41 | ], 42 | 43 | plugins: [ 44 | // required to apply rules which need type information 45 | '@typescript-eslint', 46 | 47 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files 48 | // required to lint *.vue files 49 | 'vue', 50 | 51 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 52 | // Prettier has not been included as plugin to avoid performance impact 53 | // add it as an extension for your IDE 54 | ], 55 | 56 | globals: { 57 | ga: 'readonly', // Google Analytics 58 | cordova: 'readonly', 59 | __statics: 'readonly', 60 | __QUASAR_SSR__: 'readonly', 61 | __QUASAR_SSR_SERVER__: 'readonly', 62 | __QUASAR_SSR_CLIENT__: 'readonly', 63 | __QUASAR_SSR_PWA__: 'readonly', 64 | process: 'readonly', 65 | Capacitor: 'readonly', 66 | chrome: 'readonly', 67 | }, 68 | 69 | // add your custom rules here 70 | rules: { 71 | 'prefer-promise-reject-errors': 'off', 72 | 73 | quotes: ['warn', 'single', { avoidEscape: true }], 74 | 75 | // this rule, if on, would require explicit return type on the `render` function 76 | '@typescript-eslint/explicit-function-return-type': 'off', 77 | 78 | // in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled 79 | '@typescript-eslint/no-var-requires': 'off', 80 | 81 | // The core 'no-unused-vars' rules (in the eslint:recommended ruleset) 82 | // does not work with type definitions 83 | 'no-unused-vars': 'off', 84 | 85 | // allow debugger during development only 86 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 87 | 'vue/multi-word-component-names': 'off', 88 | }, 89 | }; 90 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 网抑云音乐 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{ auth.isLogin ? auth.userInfo.nickname : '未登录' }} 23 | 24 | 25 | 26 | 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 | 78 | 106 | -------------------------------------------------------------------------------- /src/components/recommend/RecommendItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ playCount(recommend.playCount) }} 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{ recommend.name }} 38 | 39 | 40 | 90 | 115 | -------------------------------------------------------------------------------- /src/api/player/player.model.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ISong { 2 | name: string; 3 | id: number; 4 | position: number; 5 | alias: string[]; 6 | status: number; 7 | fee: number; 8 | copyrightId: number; 9 | disc: string; 10 | no: number; 11 | artists: IArtist[]; 12 | album: IAlbum; 13 | starred: boolean; 14 | popularity: number; 15 | score: number; 16 | starredNum: number; 17 | duration: number; 18 | playedNum: number; 19 | dayPlays: number; 20 | hearTime: number; 21 | ringtone: string; 22 | crbt?: any; 23 | audition?: any; 24 | copyFrom: string; 25 | commentThreadId: string; 26 | rtUrl?: any; 27 | ftype: number; 28 | rtUrls: any[]; 29 | copyright: number; 30 | transName?: any; 31 | sign?: any; 32 | mark: number; 33 | originCoverType: number; 34 | originSongSimpleData?: any; 35 | single: number; 36 | noCopyrightRcmd?: any; 37 | hMusic: IHMusic; 38 | mMusic: IHMusic; 39 | lMusic: IHMusic; 40 | bMusic: IHMusic; 41 | mvid: number; 42 | rtype: number; 43 | rurl?: any; 44 | mp3Url?: any; 45 | exclusive: boolean; 46 | privilege: IPrivilege; 47 | } 48 | 49 | declare interface IPrivilege { 50 | id: number; 51 | fee: number; 52 | payed: number; 53 | st: number; 54 | pl: number; 55 | dl: number; 56 | sp: number; 57 | cp: number; 58 | subp: number; 59 | cs: boolean; 60 | maxbr: number; 61 | fl: number; 62 | toast: boolean; 63 | flag: number; 64 | preSell: boolean; 65 | playMaxbr: number; 66 | downloadMaxbr: number; 67 | freeTrialPrivilege: IFreeTrialPrivilege; 68 | chargeInfoList: IChargeInfoList[]; 69 | } 70 | 71 | declare interface IChargeInfoList { 72 | rate: number; 73 | chargeUrl?: any; 74 | chargeMessage?: any; 75 | chargeType: number; 76 | } 77 | 78 | declare interface IFreeTrialPrivilege { 79 | resConsumable: boolean; 80 | userConsumable: boolean; 81 | } 82 | 83 | declare interface IHMusic { 84 | name?: any; 85 | id: number; 86 | size: number; 87 | extension: string; 88 | sr: number; 89 | dfsId: number; 90 | bitrate: number; 91 | playTime: number; 92 | volumeDelta: number; 93 | } 94 | 95 | declare interface IAlbum { 96 | name: string; 97 | id: number; 98 | type: string; 99 | size: number; 100 | picId: number; 101 | blurPicUrl: string; 102 | companyId: number; 103 | pic: number; 104 | picUrl: string; 105 | publishTime: number; 106 | description: string; 107 | tags: string; 108 | company: string; 109 | briefDesc: string; 110 | artist: IArtist; 111 | songs: any[]; 112 | alias: any[]; 113 | status: number; 114 | copyrightId: number; 115 | commentThreadId: string; 116 | artists: IArtist[]; 117 | subType: string; 118 | transName?: any; 119 | onSale: boolean; 120 | mark: number; 121 | picId_str: string; 122 | } 123 | 124 | declare interface IArtist { 125 | name: string; 126 | id: number; 127 | picId: number; 128 | img1v1Id: number; 129 | briefDesc: string; 130 | picUrl: string; 131 | img1v1Url: string; 132 | albumSize: number; 133 | alias: any[]; 134 | trans: string; 135 | musicSize: number; 136 | topicPerson: number; 137 | } 138 | 139 | declare interface IPlayUrl { 140 | id: number; 141 | url: string; 142 | br: number; 143 | size: number; 144 | md5: string; 145 | code: number; 146 | expi: number; 147 | type: string; 148 | gain: number; 149 | fee: number; 150 | uf?: any; 151 | payed: number; 152 | flag: number; 153 | canExtend: boolean; 154 | freeTrialInfo?: any; 155 | level: string; 156 | encodeType: string; 157 | freeTrialPrivilege: FreeTrialPrivilege; 158 | freeTimeTrialPrivilege: FreeTimeTrialPrivilege; 159 | urlSource: number; 160 | } 161 | 162 | interface FreeTimeTrialPrivilege { 163 | resConsumable: boolean; 164 | userConsumable: boolean; 165 | type: number; 166 | remainTime: number; 167 | } 168 | 169 | interface FreeTrialPrivilege { 170 | resConsumable: boolean; 171 | userConsumable: boolean; 172 | listenType?: any; 173 | } 174 | -------------------------------------------------------------------------------- /src/assets/quasar-logo-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /src/api/player/playlist.model.d.ts: -------------------------------------------------------------------------------- 1 | declare interface IPlaylistDetail { 2 | id: number; 3 | name: string; 4 | coverImgId: number; 5 | coverImgUrl: string; 6 | coverImgId_str: string; 7 | adType: number; 8 | userId: number; 9 | createTime: number; 10 | status: number; 11 | opRecommend: boolean; 12 | highQuality: boolean; 13 | newImported: boolean; 14 | updateTime: number; 15 | trackCount: number; 16 | specialType: number; 17 | privacy: number; 18 | trackUpdateTime: number; 19 | commentThreadId: string; 20 | playCount: number; 21 | trackNumberUpdateTime: number; 22 | subscribedCount: number; 23 | cloudTrackCount: number; 24 | ordered: boolean; 25 | description: string; 26 | tags: string[]; 27 | updateFrequency?: any; 28 | backgroundCoverId: number; 29 | backgroundCoverUrl?: any; 30 | titleImage: number; 31 | titleImageUrl?: any; 32 | englishTitle?: any; 33 | subscribers: any[]; 34 | subscribed: boolean; 35 | creator: Creator; 36 | tracks: Track[]; 37 | videoIds?: any; 38 | videos?: any; 39 | trackIds: TrackId[]; 40 | shareCount: number; 41 | commentCount: number; 42 | remixVideo?: any; 43 | } 44 | 45 | declare interface TrackId { 46 | id: number; 47 | v: number; 48 | t: number; 49 | at: number; 50 | alg?: any; 51 | } 52 | 53 | declare interface Track { 54 | name: string; 55 | id: number; 56 | pst: number; 57 | t: number; 58 | ar: Ar[]; 59 | alia: any[]; 60 | pop: number; 61 | st: number; 62 | rt: string; 63 | fee: number; 64 | v: number; 65 | crbt?: any; 66 | cf: string; 67 | al: Al; 68 | dt: number; 69 | h: H; 70 | m: H; 71 | l: H; 72 | a?: any; 73 | cd: string; 74 | no: number; 75 | rtUrl?: any; 76 | ftype: number; 77 | rtUrls: any[]; 78 | djId: number; 79 | copyright: number; 80 | s_id: number; 81 | mark: number; 82 | originCoverType: number; 83 | originSongSimpleData?: any; 84 | single: number; 85 | noCopyrightRcmd?: any; 86 | rtype: number; 87 | rurl?: any; 88 | mst: number; 89 | cp: number; 90 | mv: number; 91 | publishTime: number; 92 | } 93 | 94 | interface H { 95 | br: number; 96 | fid: number; 97 | size: number; 98 | vd: number; 99 | } 100 | 101 | interface Al { 102 | id: number; 103 | name: string; 104 | picUrl: string; 105 | tns: any[]; 106 | pic_str: string; 107 | pic: number; 108 | } 109 | 110 | declare interface Ar { 111 | id: number; 112 | name: string; 113 | tns: any[]; 114 | alias: any[]; 115 | } 116 | 117 | interface Creator { 118 | defaultAvatar: boolean; 119 | province: number; 120 | authStatus: number; 121 | followed: boolean; 122 | avatarUrl: string; 123 | accountStatus: number; 124 | gender: number; 125 | city: number; 126 | birthday: number; 127 | userId: number; 128 | userType: number; 129 | nickname: string; 130 | signature: string; 131 | description: string; 132 | detailDescription: string; 133 | avatarImgId: number; 134 | backgroundImgId: number; 135 | backgroundUrl: string; 136 | authority: number; 137 | mutual: boolean; 138 | expertTags: string[]; 139 | experts: Experts | any; 140 | djStatus: number; 141 | vipType: number; 142 | remarkName?: any; 143 | authenticationTypes: number; 144 | avatarDetail: AvatarDetail; 145 | avatarImgIdStr: string; 146 | backgroundImgIdStr: string; 147 | anchor: boolean; 148 | avatarImgId_str: string; 149 | } 150 | 151 | interface AvatarDetail { 152 | userType: number; 153 | identityLevel: number; 154 | identityIconUrl: string; 155 | } 156 | 157 | interface Experts { 158 | '2': string; 159 | } 160 | 161 | declare interface ISongInfo { 162 | name: string; 163 | id: number; 164 | pst: number; 165 | t: number; 166 | ar: Ar[]; 167 | alia: any[]; 168 | pop: number; 169 | st: number; 170 | rt?: any; 171 | fee: number; 172 | v: number; 173 | crbt?: any; 174 | cf: string; 175 | al: Al; 176 | dt: number; 177 | h: H; 178 | m: H; 179 | l: H; 180 | a?: any; 181 | cd: string; 182 | no: number; 183 | rtUrl?: any; 184 | ftype: number; 185 | rtUrls: any[]; 186 | djId: number; 187 | copyright: number; 188 | s_id: number; 189 | mark: number; 190 | originCoverType: number; 191 | originSongSimpleData?: any; 192 | resourceState: boolean; 193 | single: number; 194 | noCopyrightRcmd?: any; 195 | mv: number; 196 | cp: number; 197 | mst: number; 198 | rtype: number; 199 | rurl?: any; 200 | publishTime: number; 201 | } 202 | -------------------------------------------------------------------------------- /src/layouts/FooterPlayer.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ curentPlaySong?.name }} 12 | {{ artistsNames }} 13 | 14 | 15 | 16 | 17 | 18 | 25 | {{ playMode.lebal }} 26 | 27 | 34 | 41 | 48 | 49 | 50 | 51 | {{ curTimeStr }} 52 | 61 | {{ timeLen }} 62 | 63 | 64 | 65 | 66 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 168 | 173 | -------------------------------------------------------------------------------- /quasar.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /* 4 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 5 | * the ES6 features that are supported by your Node version. https://node.green/ 6 | */ 7 | 8 | // Configuration for your app 9 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js 10 | 11 | const { configure } = require('quasar/wrappers'); 12 | const path = require('path'); 13 | 14 | module.exports = configure(function (/* ctx */) { 15 | return { 16 | eslint: { 17 | // fix: true, 18 | // include = [], 19 | // exclude = [], 20 | // rawOptions = {}, 21 | warnings: true, 22 | errors: true, 23 | }, 24 | 25 | // https://v2.quasar.dev/quasar-cli-vite/prefetch-feature 26 | // preFetch: true, 27 | 28 | // app boot file (/src/boot) 29 | // --> boot files are part of "main.js" 30 | // https://v2.quasar.dev/quasar-cli-vite/boot-files 31 | boot: ['i18n'], 32 | 33 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css 34 | css: ['app.scss'], 35 | 36 | // https://github.com/quasarframework/quasar/tree/dev/extras 37 | extras: [ 38 | // 'ionicons-v4', 39 | // 'mdi-v5', 40 | // 'fontawesome-v6', 41 | // 'eva-icons', 42 | // 'themify', 43 | // 'line-awesome', 44 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 45 | 46 | 'roboto-font', // optional, you are not bound to it 47 | 'material-icons', // optional, you are not bound to it 48 | ], 49 | 50 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build 51 | build: { 52 | target: { 53 | browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'], 54 | node: 'node16', 55 | }, 56 | 57 | vueRouterMode: 'history', // available values: 'hash', 'history' 58 | 59 | alias: { 60 | api: path.join(__dirname, './src/api'), 61 | }, 62 | vitePlugins: [ 63 | [ 64 | '@intlify/vite-plugin-vue-i18n', 65 | { 66 | // if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false` 67 | // compositionOnly: false, 68 | 69 | // you need to set i18n resource including paths ! 70 | include: path.resolve(__dirname, './src/i18n/**'), 71 | }, 72 | ], 73 | /** 根据文件目录结构自动创建路由 */ 74 | /** 文档:https://www.npmjs.com/package/vite-plugin-pages */ 75 | [ 76 | 'vite-plugin-pages', 77 | { 78 | dirs: 'src/pages', 79 | exclude: ['ErrorNotFound.vue'], 80 | extendRoute(route) { 81 | if (route.path === '/home') { 82 | route.redirect = '/home/recommend'; 83 | } 84 | return route; 85 | }, 86 | }, 87 | ], 88 | ], 89 | }, 90 | 91 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer 92 | devServer: { 93 | // https: true 94 | open: true, // opens browser window automatically 95 | }, 96 | 97 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework 98 | framework: { 99 | config: {}, 100 | 101 | // iconSet: 'material-icons', // Quasar icon set 102 | // lang: 'en-US', // Quasar language pack 103 | 104 | // For special cases outside of where the auto-import strategy can have an impact 105 | // (like functional components as one of the examples), 106 | // you can manually specify Quasar components/directives to be available everywhere: 107 | // 108 | // components: [], 109 | // directives: [], 110 | 111 | // Quasar plugins 112 | plugins: [], 113 | }, 114 | 115 | // animations: 'all', // --- includes all animations 116 | // https://v2.quasar.dev/options/animations 117 | animations: [], 118 | 119 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#sourcefiles 120 | // sourceFiles: { 121 | // rootComponent: 'src/App.vue', 122 | // router: 'src/router/index', 123 | // store: 'src/store/index', 124 | // registerServiceWorker: 'src-pwa/register-service-worker', 125 | // serviceWorker: 'src-pwa/custom-service-worker', 126 | // pwaManifestFile: 'src-pwa/manifest.json', 127 | // electronMain: 'src-electron/electron-main', 128 | // electronPreload: 'src-electron/electron-preload' 129 | // }, 130 | 131 | // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr 132 | ssr: { 133 | // ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name! 134 | // will mess up SSR 135 | 136 | // extendSSRWebserverConf (esbuildConf) {}, 137 | // extendPackageJson (json) {}, 138 | 139 | pwa: false, 140 | 141 | // manualStoreHydration: true, 142 | // manualPostHydrationTrigger: true, 143 | 144 | prodPort: 3000, // The default port that the production server should use 145 | // (gets superseded if process.env.PORT is specified at runtime) 146 | 147 | middlewares: [ 148 | 'render', // keep this as last one 149 | ], 150 | }, 151 | 152 | // https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa 153 | pwa: { 154 | workboxMode: 'generateSW', // or 'injectManifest' 155 | injectPwaMetaTags: true, 156 | swFilename: 'sw.js', 157 | manifestFilename: 'manifest.json', 158 | useCredentialsForManifestTag: false, 159 | // useFilenameHashes: true, 160 | // extendGenerateSWOptions (cfg) {} 161 | // extendInjectManifestOptions (cfg) {}, 162 | // extendManifestJson (json) {} 163 | // extendPWACustomSWConf (esbuildConf) {} 164 | }, 165 | 166 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova 167 | cordova: { 168 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 169 | }, 170 | 171 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor 172 | capacitor: { 173 | hideSplashscreen: true, 174 | }, 175 | 176 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron 177 | electron: { 178 | // extendElectronMainConf (esbuildConf) 179 | // extendElectronPreloadConf (esbuildConf) 180 | 181 | inspectPort: 5858, 182 | 183 | bundler: 'packager', // 'packager' or 'builder' 184 | 185 | packager: { 186 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 187 | // OS X / Mac App Store 188 | // appBundleId: '', 189 | // appCategoryType: '', 190 | // osxSign: '', 191 | // protocol: 'myapp://path', 192 | // Windows only 193 | // win32metadata: { ... } 194 | }, 195 | 196 | builder: { 197 | // https://www.electron.build/configuration/configuration 198 | 199 | appId: 'quasar-music', 200 | }, 201 | }, 202 | 203 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex 204 | bex: { 205 | contentScripts: ['my-content-script'], 206 | 207 | // extendBexScriptsConf (esbuildConf) {} 208 | // extendBexManifestJson (json) {} 209 | }, 210 | }; 211 | }); 212 | --------------------------------------------------------------------------------
{{ auth.isLogin ? auth.userInfo.nickname : '未登录' }}