fix player + add playlist

This commit is contained in:
Stepan Fedyanin 2024-06-20 17:33:55 +05:00
parent 6680fd8792
commit 7f8d3e6f86
28 changed files with 765 additions and 129 deletions

View File

@ -7,7 +7,7 @@ ROBOTS_USER_AGENT=*
ROBOTS_ALLOW= ROBOTS_ALLOW=
ROBOTS_DISALLOW=["/"] ROBOTS_DISALLOW=["/"]
SERVICE_SELF_URL=//it-radio.flexidev.ru SERVICE_SELF_URL=//it-radio.flexidev.ru
SERVICE_URL=//it-radio.flexidev.ru SERVICE_URL=//82.97.242.49
SERVICE_URL_AUDIO=//82.97.242.49:10084 SERVICE_URL_AUDIO=//82.97.242.49:10084
SERVICE_PROTOCOL=http SERVICE_PROTOCOL=http
SERVICE_PORT=8000 SERVICE_PORT=8000

View File

@ -10,6 +10,7 @@
<AppFooter/> <AppFooter/>
</div> </div>
<Authentication/> <Authentication/>
<ChangingUser/>
</template> </template>
<script> <script>
@ -17,13 +18,15 @@ import Page404 from '@/views/Page404.vue';
import Page500 from '@/views/Page500.vue'; import Page500 from '@/views/Page500.vue';
import AppHeader from "@/components/app-header.vue"; import AppHeader from "@/components/app-header.vue";
import AppFooter from "@/components/app-footer.vue"; import AppFooter from "@/components/app-footer.vue";
import Authentication from "@/components/authentication.vue"; import Authentication from "@/components/modal/authentication.vue";
import Player from "@/components/player.vue"; import Player from "@/components/player.vue";
import ChangingUser from "@/components/modal/сhanging-user.vue";
export default { export default {
async preFetch({store, currentRoute, previousRoute, redirect, ssrContext, urlPath, publicPath}) { async preFetch({store, currentRoute, previousRoute, redirect, ssrContext, urlPath, publicPath}) {
}, },
components: { components: {
ChangingUser,
Player, Player,
Authentication, Authentication,
AppFooter, AppFooter,
@ -47,10 +50,15 @@ export default {
showErrorPage500() { showErrorPage500() {
return false return false
// return this.$store.state.showErrorPage?.response?.status === 500; // return this.$store.state.showErrorPage?.response?.status === 500;
} },
currentPlay() {
return this.$store.state.currentPlay
},
}, },
watch: {}, watch: {},
created() { created() {
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, isPlay: false});
}, },
mounted() { mounted() {
}, },

View File

@ -20,6 +20,7 @@
@import "./module/tabs.scss"; @import "./module/tabs.scss";
@import "./module/about.scss"; @import "./module/about.scss";
@import "./module/playlists.scss"; @import "./module/playlists.scss";
@import "./module/playlist.scss";
@import "./module/contacts.scss"; @import "./module/contacts.scss";
@import "./module/support.scss"; @import "./module/support.scss";
@import "./module/player.scss"; @import "./module/player.scss";

View File

@ -95,13 +95,15 @@
&:hover::after { &:hover::after {
background: svg-load('./assets/img/icon/ArrowRight.svg', stroke=#232323) no-repeat 100%; background: svg-load('./assets/img/icon/ArrowRight.svg', stroke=#232323) no-repeat 100%;
}
&.m--active::after,
&:active::after {
width: 45px; width: 45px;
height: 20px; height: 20px;
} }
&.m--active,
&:active {
background:transparent;
color: var(--color-white);
}
} }
&.m--w-100 { &.m--w-100 {
@ -135,6 +137,7 @@
pad: 0; pad: 0;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
padding: 0;
&:hover{ &:hover{
background: transparent; background: transparent;
} }

View File

@ -26,6 +26,24 @@
.input { .input {
@mixin input; @mixin input;
} }
&__input{
&.m--search {
position: relative;
&:before{
content: '';
width: 24px;
height: 24px;
top: 16px;
left: 16px;
position: absolute;
background: svg-load('./assets/img/icon/search-icon.svg') no-repeat 100%;
}
.input{
padding: 1rem 1.2rem 1rem 3rem;
border: 1px solid var(--color-primary);
}
}
}
&__checkbox { &__checkbox {
&-label{ &-label{
padding-left: 50px; padding-left: 50px;

View File

@ -48,7 +48,13 @@
max-width: 420px; max-width: 420px;
} }
} }
&__content{
min-width: 360px;
}
&__title{
font-size: 2rem;
margin-bottom: 2.5rem;
}
&__close{ &__close{
width: 30px; width: 30px;
height: 30px; height: 30px;

View File

@ -30,11 +30,13 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 20px; margin-left: 20px;
min-width: 135px;
gap: 0.25rem; gap: 0.25rem;
span { span {
color: var(--color-white-opacity); color: var(--color-white-opacity);
} }
&.m--skeleton{
min-width: 135px;
}
} }
&__favorites { &__favorites {

View File

@ -0,0 +1,81 @@
.playlist {
position: relative;
z-index: 2;
&-roster {
display: flex;
flex-wrap: wrap;
gap: calc(var(--space-between-block) / 2);
width: 100%;
}
&-edit{
position: relative;
z-index: 2;
&__search{
margin-bottom: var(--space-between-block);
}
&__search{
min-width: 320px;
width: 40%;
}
&__list{
display: flex;
gap: var(--space-between-block);
}
&__item{
width: calc(50% - var(--space-between-block) / 2) ;
}
&__title{
color: transparent;
font-size: 2rem;
background-clip: text;
background-image: var(--linear-gradient);
margin-bottom: 1rem;
}
}
&-item {
z-index: 2;
position: relative;
display: flex;
flex-direction: column;
min-width: 220px;
cursor: url("./assets/img/icon/cursor.svg"), auto;
&__cover {
background: svg-load('./assets/img/icon/playlist-item.svg', fill=$color-white) no-repeat 100%;
border-radius: 10px;
margin-bottom: 10px;
transition: all .3s ease;
min-height: 220px;
&:hover{
background: svg-load('./assets/img/icon/playlist-item.svg', fill=$color-white-opacity) no-repeat 100%;
border-radius: 10px;
margin-bottom: 10px;
}
}
&.m--create &__cover {
position: relative;
background: var(--bg-opacity);
&:after {
content: '';
width: 63px;
height: 63px;
background: svg-load('./assets/img/icon/playlist-item-create.svg', fill=$color-white-opacity) no-repeat 100%;
transition: all .3s ease;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
&:hover{
background: var(--color-primary);
&:after{
background: svg-load('./assets/img/icon/playlist-item-create.svg', fill=$color-white) no-repeat 100%;
}
}
}
}
}

View File

@ -2,6 +2,7 @@
&__title { &__title {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding: 0; padding: 0;
font-size: 1.5rem;
} }
&__tabs{ &__tabs{
margin-top: var(--space-between-block); margin-top: var(--space-between-block);

View File

@ -4,6 +4,9 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 0 var(--space-between-block); gap: 0 var(--space-between-block);
} }
.m--column &-item{
width: 100%;
}
&-item { &-item {
width: calc(50% - var(--space-between-block) / 2); width: calc(50% - var(--space-between-block) / 2);
position: relative; position: relative;
@ -12,38 +15,48 @@
z-index: 2; z-index: 2;
border-left: 3px solid transparent; border-left: 3px solid transparent;
padding: 10px; padding: 10px;
transition: all .5s ease; transition: border .5s ease, background .5s ease;
background: transparent; background: transparent;
@mixin responsive-m {
width: 100%;
}
&.m--select &__selected { &.m--select &__selected {
margin-right: 20px; margin-right: 20px;
width: 35px; width: 35px;
height: 33px; height: 33px;
&.m--stop { &.m--stop {
path { path {
animation-play-state: paused; animation-play-state: paused;
} }
} }
} }
&__selected { &__selected {
width: 0; width: 0;
overflow: hidden; overflow: hidden;
transition: all .2s ease; transition: all .2s ease;
path:nth-child(n) { path:nth-child(n) {
animation: play-transform 3s infinite linear; animation: play-transform 3s infinite linear;
} }
path:nth-child(2n) { path:nth-child(2n) {
animation: play-transform 2s infinite linear; animation: play-transform 2s infinite linear;
} }
path:nth-child(3n) { path:nth-child(3n) {
animation: play-transform 1.5s infinite linear; animation: play-transform 1.5s infinite linear;
} }
} }
&:hover { &:hover {
cursor: url("./assets/img/icon/cursor.svg"), auto; cursor: url("./assets/img/icon/cursor.svg"), auto;
border-color: var(--color-primary); border-color: var(--color-primary);
background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%); background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
} }
&__btn { &__btn {
min-height: 0; min-height: 0;
min-width: 0; min-width: 0;
@ -53,11 +66,13 @@
border: none; border: none;
background: var(--color-primary); background: var(--color-primary);
position: relative; position: relative;
&.m--small { &.m--small {
background: transparent; background: transparent;
width: 25px; width: 25px;
height: 25px; height: 25px;
} }
&.m--favorites { &.m--favorites {
&:after { &:after {
content: ''; content: '';
@ -75,6 +90,7 @@
background-size: contain; background-size: contain;
} }
} }
&.m--add { &.m--add {
&:after { &:after {
content: ''; content: '';
@ -92,6 +108,24 @@
background-size: contain; background-size: contain;
} }
} }
&.m--already{
&:after {
content: '';
position: absolute;
width: 25px;
height: 25px;
margin: auto;
background: svg-load('./assets/img/icon/icon-trash.svg', stroke=$color-primary) no-repeat 100%;
background-size: contain;
transition: all .3s ease;
}
&:hover:after {
background: svg-load('./assets/img/icon/icon-trash.svg', stroke=#FFF) no-repeat 100%;
background-size: contain;
}
}
&.m--play { &.m--play {
&:after { &:after {
content: ''; content: '';
@ -128,6 +162,7 @@
} }
} }
} }
&__info { &__info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -135,10 +170,12 @@
color: var(--color-white-opacity); color: var(--color-white-opacity);
line-height: 21px; line-height: 21px;
margin-left: 20px; margin-left: 20px;
span { span {
color: var(--color-white); color: var(--color-white);
} }
} }
&__tools { &__tools {
margin-left: auto; margin-left: auto;
display: flex; display: flex;
@ -147,6 +184,7 @@
} }
} }
} }
@keyframes play-transform { @keyframes play-transform {
0% { 0% {
transform: translateY(0); transform: translateY(0);

View File

@ -1,8 +1,15 @@
.tabs { .tabs {
display: flex; display: flex;
flex-wrap: wrap;
margin-bottom: var(--space-between-block); margin-bottom: var(--space-between-block);
&.m--btns{ &.m--btns{
gap: 10px; gap: 10px;
@mixin responsive-xs {
.button{
width: 100%;
}
}
} }
&__item { &__item {
border: none; border: none;

View File

@ -6,16 +6,17 @@ $document-width-xs: 640px;
$document-width-xxs: 480px; $document-width-xxs: 480px;
$color-primary: #5E5BFC; $color-primary: #5E5BFC;
$color-white: #FFFFFF; $color-white: #FFFFFF;
$color-white-opacity: #BBB9CA;
:root { :root {
--color-black: #232323; --color-black: #232323;
--color-white: $color-white; --color-white: $color-white;
--color-white-darker: #E7E7E7; --color-white-darker: #E7E7E7;
--color-white-opacity: #BBB9CA; --color-white-opacity: $color-white-opacity;
--bg-wrapper-modal: #000000E5; --bg-wrapper-modal: #000000E5;
--color-primary: $color-primary; --color-primary: $color-primary;
--color-black-cc: #000000CC; --color-black-cc: #000000CC;
--color-emmit: #E81717; --color-emmit: #E81717;
--bg-opacity: rgba(255, 255, 255, 0.15); --bg-opacity: #74767D;
--base-fz: 18px; --base-fz: 18px;
--base-lh: 1.3; --base-lh: 1.3;
--font-family-base: 'Manrope', 'Tahoma', sans-serif; --font-family-base: 'Manrope', 'Tahoma', sans-serif;

View File

@ -126,7 +126,7 @@ export default {
methods:{ methods:{
handlerClick(methods){ handlerClick(methods){
if (methods==='login'){ if (methods==='login'){
this.$store.dispatch('setShowAuthModal', true); this.$store.dispatch('setModal', {auth: true});
} }
if (methods==='profile'){ if (methods==='profile'){
this.next('profile') this.next('profile')

View File

@ -137,7 +137,7 @@ export default {
}, },
computed: { computed: {
show() { show() {
return this.$store.state.showAuthModal return this.$store.state.modal.auth
} }
}, },
methods: { methods: {
@ -192,7 +192,7 @@ export default {
}, },
close() { close() {
this.$store.dispatch('setShowAuthModal', false); this.$store.dispatch('setModal', {auth: false});
}, },
next(name) { next(name) {
this.$router.push({name}); this.$router.push({name});

View File

@ -0,0 +1,109 @@
<template>
<vue-final-modal
v-model="show"
class="modal__container"
content-class="modal__block"
content-transition="vfm-fade"
overlay-transition="vfm-fade"
:clickToClose="false"
@click-outside="close()"
>
<button
class="button modal__close"
@click="close"
>
</button>
<div class="modal__content">
<div class="modal__title">Изменение данных</div>
<FormKit
v-model="formData"
type="form"
data-loading="showLoaderSending"
form-class="$reset registration__form form"
submit-label="Войти"
:disabled="showLoaderSending"
:loading="showLoaderSending ? true : undefined"
:submit-attrs="{
inputClass: '$reset button m--white m--w-100',
wrapperClass: '$reset registration__form-submit form__submit',
outerClass: '$reset',
}"
@submit="submitHandler"
>
<FormKitSchema :schema="userForm"/>
</FormKit>
</div>
</vue-final-modal>
</template>
<script>
import {app} from "@/services";
export default {
name: 'recovery',
data(){
return{
userForm:[
{
$formkit: 'text',
name: 'email',
label: 'Ваша почта',
placeholder: 'Ваша почта',
validation: 'required',
outerClass: 'field--required'
},
{
$formkit: 'password',
name: 'old_password',
label: 'Введите старый пароль',
placeholder: 'Введите старый пароль',
validation: 'required',
outerClass: 'field--required'
},
{
$formkit: 'password',
name: 'password',
label: 'Введите новый пароль',
placeholder: 'Введите новый пароль',
validation: 'required',
outerClass: 'field--required'
}
],
formData: {},
showLoaderSending: false,
}
},
computed: {
user() {
return this.$store.state.user;
},
show(){
return this.$store.state.modal.changingUser;
}
},
mounted() {
this.formData.email = this.user.email;
},
methods:{
submitHandler(data, node){
this.showLoaderSending = true;
app.updateUser(this.formData).then(user=>{
this.showLoaderSending = false;
this.$store.dispatch('setUser', user);
}).catch(err=>{
this.showLoaderSending = false;
node.setErrors(
[err.detail],
err.error
)
})
},
close() {
this.$store.dispatch('setModal', {changingUser: false});
},
next(name){
this.$router.push({name});
}
}
}
</script>

View File

@ -14,7 +14,7 @@
</button> </button>
</template> </template>
<div class="player__executor"> <div class="player__executor">
<q-skeleton v-if="loaderPlay"/> <q-skeleton v-if="loaderPlay" class="player__executor m--skeleton"/>
<template v-else> <template v-else>
{{ currentPlay.title || '—' }} {{ currentPlay.title || '—' }}
</template> </template>
@ -35,7 +35,7 @@
v-model="isUserMusic" v-model="isUserMusic"
type="toggle" type="toggle"
label="Включить мою музыку" label="Включить мою музыку"
:disabled="!user?.id" :disabled="!user?.id || userSongList.length === 0"
/> />
<q-skeleton v-if="loaderPlay" class="player__tools m--skeleton"/> <q-skeleton v-if="loaderPlay" class="player__tools m--skeleton"/>
<div v-else class="player__volume"> <div v-else class="player__volume">
@ -71,7 +71,6 @@ export default {
components: {}, components: {},
data() { data() {
return { return {
audioUrl: 'http://82.97.242.49:18000/radio.mp3',
isFavorites: false, isFavorites: false,
isPlayRadio: false, isPlayRadio: false,
connection: null, connection: null,
@ -108,7 +107,7 @@ export default {
if (this.isUserMusic === this.currentPlay.live) { if (this.isUserMusic === this.currentPlay.live) {
this.isUserMusic = !this.currentPlay.live; this.isUserMusic = !this.currentPlay.live;
} }
if (to.id !== from.id && this.user?.id) { if (this.user?.id && to.id !== from.id) {
this.checkSongIsFavorite(); this.checkSongIsFavorite();
} }
if (!this.currentPlay.live && to.id !== from.id) { if (!this.currentPlay.live && to.id !== from.id) {
@ -116,6 +115,14 @@ export default {
} }
}, },
}, },
'userSongList': {
immediate: false,
handler(to, from) {
if (this.user?.id && to.length !== from.length) {
this.checkSongIsFavorite();
}
},
},
'isUserMusic': { 'isUserMusic': {
immediate: false, immediate: false,
handler() { handler() {
@ -133,16 +140,19 @@ export default {
this.$store.dispatch('initPlayer'); this.$store.dispatch('initPlayer');
if (this.user?.id) { if (this.user?.id) {
this.checkSongIsFavorite(); this.checkSongIsFavorite();
this.getSongList();
} }
this.playerInfo.progress = this.currentPlay.live ? 100 : 0; this.playerInfo.progress = this.currentPlay.live ? 100 : 0;
console.log(this.currentPlay)
if (!this.currentPlay.live && this.userSongList?.length > 0) { if (!this.currentPlay.live && this.userSongList?.length > 0) {
this.$store.dispatch('setCurrentPlay', {...this.userSongList[0], live: false}); this.$store.dispatch('setCurrentPlay', {...this.userSongList[this.currentPlay.currentIndex || 0], live: false});
this.getAudio(this.userSongList[0]?.azura_id); this.getAudio(this.userSongList[this.currentPlay.currentIndex || 0]?.azura_id);
if (this.player.target) { if (this.player.target) {
this.player.target.addEventListener('timeupdate', this.updateProgress) this.player.target.addEventListener('timeupdate', this.updateProgress)
} }
} }
if (!this.user?.id){
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, live: true, isLoader: true, currentIndex: null});
}
}, },
methods: { methods: {
connectionPlayer() { connectionPlayer() {
@ -154,7 +164,6 @@ export default {
this.connection.onHandler(this.getPlaying); this.connection.onHandler(this.getPlaying);
}, },
checkSongIsFavorite() { checkSongIsFavorite() {
console.debug(this.currentPlay.azura_id || this.currentPlay.id, this.currentPlay.title)
app.getCheckFavoriteSong(this.currentPlay.azura_id || this.currentPlay.id).then(res => { app.getCheckFavoriteSong(this.currentPlay.azura_id || this.currentPlay.id).then(res => {
this.isFavorites = res.is_favorite; this.isFavorites = res.is_favorite;
}).catch(err => { }).catch(err => {
@ -177,18 +186,19 @@ export default {
if (data.np.station.listen_url !== this.player.target.src) { if (data.np.station.listen_url !== this.player.target.src) {
console.log('data.np.station.listen_url', data.np.station.listen_url) console.log('data.np.station.listen_url', data.np.station.listen_url)
this.$store.dispatch('changePlayer', data.np.station.listen_url); this.$store.dispatch('changePlayer', data.np.station.listen_url);
}
const params = { const params = {
...this.currentPlay, ...this.currentPlay,
...data.np.now_playing.song, ...data.np.now_playing.song,
azura_id: data.np.now_playing.song.id, azura_id: data.np.now_playing.song.id,
isLoader: false, isLoader: false,
live: true, live: true,
currentIndex: null
} }
delete params.unique_id; delete params.unique_id;
this.$store.dispatch('setCurrentPlay', params); this.$store.dispatch('setCurrentPlay', params);
} }
} }
}
}, },
updateProgress(e) { updateProgress(e) {
this.playerInfo = { this.playerInfo = {
@ -197,10 +207,16 @@ export default {
currentTime: this.player.target.currentTime currentTime: this.player.target.currentTime
} }
if (this.player.target.currentTime === this.player.target.duration){ if (this.player.target.currentTime === this.player.target.duration){
this.getAudio(this.userSongList[1]?.azura_id); let currentIndex = this.currentPlay.currentIndex + 1;
if (!this.userSongList[currentIndex]?.azura_id || currentIndex === null){
currentIndex = 0;
}
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, ...this.userSongList[currentIndex], currentIndex})
} }
}, },
handlerPlay() { handlerPlay() {
console.log(this.currentPlay)
console.log(this.player)
this.$store.dispatch('handlerPlayer', {play: true}); this.$store.dispatch('handlerPlayer', {play: true});
}, },
handlerPause() { handlerPause() {
@ -209,13 +225,17 @@ export default {
getSongList() { getSongList() {
app.getFavoriteList().then(res => { app.getFavoriteList().then(res => {
this.$store.dispatch('setUserFavorite', {songs: res}) this.$store.dispatch('setUserFavorite', {songs: res})
console.log('res.length' ,res.length)
if (res.length === 0){
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, live: true, isLoader: true});
}
}).catch(err => { }).catch(err => {
console.error(err) console.error(err)
}) })
}, },
handlerFavorites() { handlerFavorites() {
if (this.user?.id) { if (this.user?.id) {
const params = {...this.currentPlay, azura_id: this.currentPlay.id}; const params = {...this.currentPlay, azura_id: Number(this.currentPlay.id)?this.currentPlay.azura_id:this.currentPlay.id};
if (!this.isFavorites) { if (!this.isFavorites) {
delete params.id; delete params.id;
app.createFavoriteForUser(params).then(() => { app.createFavoriteForUser(params).then(() => {
@ -252,7 +272,6 @@ export default {
app.getAudio(id).then(res => { app.getAudio(id).then(res => {
const blob = new Blob([res], {type: 'application/audio'}); const blob = new Blob([res], {type: 'application/audio'});
const audioUrl = URL.createObjectURL(blob); const audioUrl = URL.createObjectURL(blob);
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, isLoader: false})
this.$store.dispatch('changePlayer', audioUrl); this.$store.dispatch('changePlayer', audioUrl);
this.player.target.addEventListener('timeupdate', this.updateProgress); this.player.target.addEventListener('timeupdate', this.updateProgress);
@ -263,6 +282,7 @@ export default {
this.playerInfo.duration = decodedData.duration; this.playerInfo.duration = decodedData.duration;
}); });
}); });
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, isLoader: false})
}).catch(err => { }).catch(err => {
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, isLoader: false}) this.$store.dispatch('setCurrentPlay', {...this.currentPlay, isLoader: false})
console.debug(err) console.debug(err)
@ -271,9 +291,10 @@ export default {
changeLive() { changeLive() {
if (this.currentPlay.live) { if (this.currentPlay.live) {
console.log('избранное') console.log('избранное')
this.getAudio(this.userSongList[0].azura_id);
this.playerInfo.progress = 0; this.playerInfo.progress = 0;
this.$store.dispatch('setCurrentPlay', {...this.userSongList[0], live: false, isLoader: true}); const params = {...this.userSongList[this.currentPlay.currentIndex || 0], live: false, isLoader: true};
if (!this.currentPlay.currentIndex) params.currentIndex = 0;
this.$store.dispatch('setCurrentPlay', params);
} else { } else {
this.playerInfo.progress = 100; this.playerInfo.progress = 100;
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, live: true, isLoader: true}); this.$store.dispatch('setCurrentPlay', {...this.currentPlay, live: true, isLoader: true});

View File

@ -0,0 +1,23 @@
<template>
<div class="playlist-item" @click="selectPlaylist">
<div class="playlist-item__cover"></div>
<div>{{playlist.title}}</div>
</div>
</template>
<script>
export default {
name: 'playlist-item',
props:{
playlist:{
type: Object,
default: () => {}
}
},
methods:{
selectPlaylist(){
this.$emit('selectPlaylist', this.playlist)
}
}
}
</script>

View File

@ -0,0 +1,37 @@
<template>
<div class="playlist-roster">
<PlaylistItem
v-for="item in list"
:key="`playlist_${item.id}`"
:playlist="item"
@selectPlaylist="selectPlaylist"
/>
<div class="playlist-item m--create" @click="createPlaylist">
<div class="playlist-item__cover"></div>
<div>Создать плейлист</div>
</div>
</div>
</template>
<script>
import PlaylistItem from "@/components/playlist-item.vue";
export default {
name: 'playlist-roster',
components: {PlaylistItem},
props: {
list: {
type: Array,
default: () => []
}
},
methods: {
createPlaylist() {
this.$emit('createPlaylist')
},
selectPlaylist(params) {
this.$router.push({name: 'playlist', params: {id: params.id}})
}
}
}
</script>

View File

@ -27,7 +27,7 @@
</template> </template>
<script> <script>
import RubricModal from "@/components/rubric-modal.vue"; import RubricModal from "@/components/modal/rubric-modal.vue";
import {app} from "@/services"; import {app} from "@/services";
export default { export default {

View File

@ -21,7 +21,7 @@
</div> </div>
<div class="song-item__tools"> <div class="song-item__tools">
<button class="button song-item__btn m--small m--favorites" @click.stop="removeSong"></button> <button class="button song-item__btn m--small m--favorites" @click.stop="removeSong"></button>
<button class="button song-item__btn m--small m--add"></button> <button class="button song-item__btn m--small m--add" :class="songAlreadyAddPlaylist&&'m--already'" @click.stop="songAlreadyAddPlaylist?removeToPlaylist:addPlaylist"></button>
</div> </div>
</div> </div>
</template> </template>
@ -48,6 +48,12 @@ export default {
return false return false
} }
}, },
songAlreadyAddPlaylist:{
type: Boolean,
default(){
return false
}
},
}, },
watch: { watch: {
'playSong': { 'playSong': {
@ -74,6 +80,12 @@ export default {
}, },
removeSong(){ removeSong(){
this.$emit('removeSong', this.song); this.$emit('removeSong', this.song);
},
addPlaylist(){
this.$emit('addPlaylist', this.song);
},
removeToPlaylist(){
this.$emit('removePlaylist', this.song);
} }
} }
} }

View File

@ -6,10 +6,13 @@
:song="song" :song="song"
:playSong="song.azura_id === currentPlay.azura_id? currentPlay.isPlay: false" :playSong="song.azura_id === currentPlay.azura_id? currentPlay.isPlay: false"
:selectSong="song.azura_id === currentPlay.azura_id" :selectSong="song.azura_id === currentPlay.azura_id"
:songAlreadyAddPlaylist="songAlreadyAdd"
@selectSong="handlerSelectSong" @selectSong="handlerSelectSong"
@playSong="handlerPlaySong" @playSong="handlerPlaySong"
@pauseSong="handlerPauseSong" @pauseSong="handlerPauseSong"
@removeSong="removeSong" @removeSong="removeSong"
@addPlaylist="addPlaylist"
@removePlaylist="removePlaylist"
/> />
</div> </div>
</template> </template>
@ -24,6 +27,10 @@ export default {
songList: { songList: {
type: Array, type: Array,
default: () => [] default: () => []
},
songAlreadyAdd: {
type: Boolean,
default: () => false
} }
}, },
data() { data() {
@ -36,7 +43,9 @@ export default {
}, },
methods: { methods: {
handlerSelectSong(params) { handlerSelectSong(params) {
this.$store.dispatch('setCurrentPlay', {...this.currentPlay, ...params, isLoader: true}); const data = {...this.currentPlay, ...params, isLoader: true};
data.currentIndex = this.songList.findIndex(el=>el.azura_id === params.azura_id);
this.$store.dispatch('setCurrentPlay', data);
console.log('handlerSelectSong') console.log('handlerSelectSong')
}, },
handlerPlaySong(params) { handlerPlaySong(params) {
@ -51,6 +60,12 @@ export default {
}, },
removeSong(song){ removeSong(song){
this.$emit('removeSong', song) this.$emit('removeSong', song)
},
addPlaylist(song){
this.$emit('addPlaylist', song)
},
removePlaylist(song){
this.$emit('removePlaylist', song)
} }
} }
} }

View File

@ -6,6 +6,8 @@ import contacts from "@/views/contacts.vue";
import podcasts from "@/views/podcasts.vue"; import podcasts from "@/views/podcasts.vue";
import about from "@/views/about.vue"; import about from "@/views/about.vue";
import profile from "@/views/profile.vue"; import profile from "@/views/profile.vue";
import playlist from "@/views/playlist.vue";
import playlistCreate from "@/views/playlist-edit.vue";
const routes = [ const routes = [
@ -80,6 +82,37 @@ const routes = [
title: 'Личный кабинет', title: 'Личный кабинет',
isAuth: false isAuth: false
}, },
children: [
{
path: 'playlist/:id',
name: 'playlist',
component: playlist,
meta: {
title: 'Плейлист',
requiresAuth: true
},
props: true,
},
{
path: 'playlist-create/:id',
name: 'playlist-create',
component: playlistCreate,
meta: {
title: 'Плейлист',
requiresAuth: true
},
props: true,
},{
path: 'playlist-edit/:id',
name: 'playlist-edit',
component: playlistCreate,
meta: {
title: 'Плейлист',
requiresAuth: true
},
props: true,
},
]
}, },
// { // {

View File

@ -27,6 +27,14 @@ export default class extends REST {
}); });
} }
static updateUser(params) {
return this._post(`user/update_user`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Не удалось создать пользователя');
});
}
static getTeams(station, params) { static getTeams(station, params) {
return this._get(`radio/teams`, params, {}).then((data) => { return this._get(`radio/teams`, params, {}).then((data) => {
return data; return data;
@ -42,7 +50,13 @@ export default class extends REST {
throw new RESTError(error, 'Ошибка при проверке песни'); throw new RESTError(error, 'Ошибка при проверке песни');
}); });
} }
static getAllSong(){
return this._get(`radio/song/get_all_song`, {}, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получениии всех песен');
});
}
static getAudio(id){ static getAudio(id){
return this._get(`radio/song/get_audio/${id}`, {}, {}, false, true).then((data) => { return this._get(`radio/song/get_audio/${id}`, {}, {}, false, true).then((data) => {
return data; return data;
@ -96,8 +110,35 @@ export default class extends REST {
}); });
} }
static getPlaylists() {
return this._get(`radio/playlists`, {}, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getPlaylist(id) {
return this._get(`radio/playlists/${id}`, {}, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static addSongToPlaylist(params) {
return this._post(`radio/playlists/add_to_playlist`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static createPlaylists() {
return this._post(`radio/playlists/create_playlist`, {}, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getNews(station, params) { static getNews(station, params) {

View File

@ -14,6 +14,10 @@ export default createStore({
token: null, token: null,
refreshToken: null, refreshToken: null,
user: null, user: null,
modal:{
auth: false,
changingUser: false
},
showAuthModal: false, showAuthModal: false,
station: { station: {
id: 1 id: 1
@ -23,6 +27,7 @@ export default createStore({
isLoader: false, isLoader: false,
live: true, live: true,
volume: 50, volume: 50,
currentIndex: null,
}, },
player: { player: {
target: null, target: null,
@ -50,8 +55,8 @@ export default createStore({
setCurrentPlay(state, song) { setCurrentPlay(state, song) {
state.currentPlay = song; state.currentPlay = song;
}, },
setShowAuthModal(state, show) { setModal(state, show) {
state.showAuthModal = show state.modal = {...state.modal, ...show}
}, },
setPlayer(state, params) { setPlayer(state, params) {
state.player = {...state.player, ...params} state.player = {...state.player, ...params}
@ -117,8 +122,8 @@ export default createStore({
setCurrentPlay(context, song) { setCurrentPlay(context, song) {
context.commit('setCurrentPlay', song); context.commit('setCurrentPlay', song);
}, },
setShowAuthModal(context, show) { setModal(context, show) {
context.commit('setShowAuthModal', show); context.commit('setModal', show);
}, },
setPlayer(context, params) { setPlayer(context, params) {
context.commit('setPlayer', params); context.commit('setPlayer', params);

View File

@ -0,0 +1,108 @@
<template>
<div class="playlist-edit">
<AppBreadcrumbs
:breadcrumbs="[
{ name: 'Главная', route: { name: 'home' } },
{ name: 'Личный кабинет', route: { name: 'profile' } },
$route.name==='playlist-create'?
{ name: 'Добавление плейлиста', route: { name: 'playlist-create' } }:
{ name: 'Редактирование плейлиста', route: { name: 'playlist-edit' } },
]"/>
<h1 class="h2">{{ $route.name==='playlist-create'?'Новый плейлист':playlist.name }}</h1>
<FormKit
v-model="playlist.name"
type="text"
outer-class="playlist-edit__search"
inner-class="m--search"
placeholder="Название плейлиста"
></FormKit>
<div class="playlist-edit__list">
<div class="playlist-edit__item">
<div class="playlist-edit__title">Поиск по каталогу</div>
<template v-if="showLoaderSongs">
<div class="loader">
<div class="spinner"/>
Загрузка данных
</div>
</template>
<SongList v-else-if="songs.length>0" :songList="songs" class="m--column" @addPlaylist="addPlaylist"/>
<div v-else>Каталог музыки пуст!</div>
</div>
<div class="playlist-edit__item">
<div class="playlist-edit__title">Добавленные треки</div>
<template v-if="showLoaderPlaylist">
<div class="loader">
<div class="spinner"/>
Загрузка данных
</div>
</template>
<SongList v-else-if="playlist.song?.length>0" :songList="playlist.song" class="m--column" :songAlreadyAdd="true" @removePlaylist="removePlaylist"/>
<div v-else>Добавьте музыку в плейлист!</div>
</div>
</div>
</div>
</template>
<script>
import {app} from "@/services";
import AppBreadcrumbs from "@/components/app-breadcrumbs.vue";
import SongList from "@/components/song-list.vue";
export default {
name: 'playlist-edit',
components: {SongList, AppBreadcrumbs},
data() {
return {
playlist: {},
songs: [],
showLoaderSongs: false,
showLoaderPlaylist: false,
}
},
created() {
this.getAllSong();
this.getPlaylist();
},
methods:{
getPlaylist(){
this.showLoaderPlaylist = true;
app.getPlaylist(this.$route.params.id).then(res=>{
this.showLoaderPlaylist = false;
this.playlist = res;
}).catch(err=>{
this.showLoaderPlaylist = false;
console.error(err)
})
},
getAllSong(){
this.showLoaderSongs = true;
app.getAllSong().then(res=>{
this.showLoaderSongs = false;
this.songs = res;
}).catch(err=>{
this.showLoaderSongs = false;
console.error(err)
})
},
addPlaylist(song){
console.log(song)
const params = {
playlist_id: this.$route.params.id,
azura_id: song.azura_id
}
this.showLoaderPlaylist = true;
app.addSongToPlaylist(params).then(()=>{
this.showLoaderPlaylist = false;
this.getPlaylist();
}).catch(err=>{
this.showLoaderPlaylist = false;
console.error(err)
})
},
removePlaylist(){
}
}
}
</script>

View File

@ -0,0 +1,11 @@
<template>
<div class="playlist">
</div>
</template>
<script>
export default {
name: 'playlist'
}
</script>

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="app__content profile"> <div class="app__content profile">
<template v-if="$route.name !== 'playlist-edit'">
<AppBreadcrumbs <AppBreadcrumbs
:breadcrumbs="[ :breadcrumbs="[
{ name: 'Главная', route: { name: 'home' } }, { name: 'Главная', route: { name: 'home' } },
@ -7,7 +8,7 @@
]" ]"
/> />
<h1 class="h2 profile__title">{{ user.email }}</h1> <h1 class="h2 profile__title">{{ user.email }}</h1>
<button class="button m--text-link">Редактировать профиль</button> <button class="button m--text-link" @click="showRecovery">Редактировать профиль</button>
<div class="profile__tabs tabs m--btns"> <div class="profile__tabs tabs m--btns">
<button <button
v-for="item in tabsItems" v-for="item in tabsItems"
@ -19,8 +20,10 @@
{{ item.label }} {{ item.label }}
</button> </button>
</div> </div>
</template>
<template v-if="$route.name === 'profile'">
<template v-if="currentTabsItem==='music'"> <template v-if="currentTabsItem==='music'">
<template v-if="showLoader"> <template v-if="showLoaderSong">
<div class="loader"> <div class="loader">
<div class="spinner"/> <div class="spinner"/>
Загрузка данных Загрузка данных
@ -28,6 +31,21 @@
</template> </template>
<SongList v-else :songList="userFavorite.songs" @removeSong="removeFavorites"/> <SongList v-else :songList="userFavorite.songs" @removeSong="removeFavorites"/>
</template> </template>
<template v-if="currentTabsItem==='playlists'">
<template v-if="showLoaderPlaylist">
<div class="loader">
<div class="spinner"/>
Загрузка данных
</div>
</template>
<PlaylistRoster
v-else
:list="userFavorite.playlist"
@createPlaylist="createPlayList"
/>
</template>
</template>
<routerView v-else/>
</div> </div>
</template> </template>
@ -35,10 +53,11 @@
import AppBreadcrumbs from "@/components/app-breadcrumbs.vue"; import AppBreadcrumbs from "@/components/app-breadcrumbs.vue";
import SongList from "@/components/song-list.vue"; import SongList from "@/components/song-list.vue";
import {app} from "@/services"; import {app} from "@/services";
import PlaylistRoster from "@/components/playlist-roster.vue";
export default { export default {
name: 'profile', name: 'profile',
components: {SongList, AppBreadcrumbs}, components: {PlaylistRoster, SongList, AppBreadcrumbs},
data() { data() {
return { return {
currentTabsItem: 'music', currentTabsItem: 'music',
@ -56,8 +75,9 @@ export default {
name: 'playlists' name: 'playlists'
}, },
], ],
songList: [], showLoaderSong: false,
showLoader: true, showLoaderPlaylist: false,
showLoaderPodcast: true,
} }
}, },
computed: { computed: {
@ -78,31 +98,66 @@ export default {
this.currentTabsItem = 'music'; this.currentTabsItem = 'music';
} }
}, },
},
'$route.name': {
immediate: false,
handler(to) {
if (to === 'playlist') {
this.currentTabsItem = 'playlists'
} }
}, },
},
},
created() { created() {
this.getSongList(); this.getSongList();
this.getPlaylists();
}, },
methods: { methods: {
getSongList() { getSongList() {
this.showLoader = true; this.showLoaderSong = true;
app.getFavoriteList().then(res => { app.getFavoriteList().then(res => {
this.showLoader = false; this.showLoaderSong = false;
this.$store.dispatch('setUserFavorite', {songs: res}) this.$store.dispatch('setUserFavorite', {songs: res});
}).catch(err => { }).catch(err => {
this.showLoader = false; this.showLoaderSong = false;
console.error(err) console.error(err)
}) })
}, },
changeTab(tab) { changeTab(tab) {
this.currentTabsItem = tab; this.currentTabsItem = tab;
if (this.$route.name === 'profile') {
this.$router.push({name: this.$route.name, hash: `#${tab}`}); this.$router.push({name: this.$route.name, hash: `#${tab}`});
} else {
this.$router.push({name: 'profile', hash: `#${tab}`});
}
}, },
removeFavorites(song) { removeFavorites(song) {
this.showLoaderSong = true;
app.removeFavorites(song).then(() => { app.removeFavorites(song).then(() => {
this.showLoaderSong = false;
this.getSongList(); this.getSongList();
}).catch(err => { }).catch(err => {
this.showLoader = false; this.showLoaderSong = false;
console.error(err)
})
},
showRecovery() {
this.$store.dispatch('setModal', {changingUser: true});
},
getPlaylists() {
this.showLoaderPlaylist = true;
app.getPlaylists().then(res => {
this.showLoaderPlaylist = false;
this.$store.dispatch('setUserFavorite', {playlist: res});
}).catch(err => {
this.showLoaderPlaylist = false;
console.error(err)
})
},
createPlayList() {
app.createPlaylists().then(res=>{
this.$router.push({name: 'playlist-edit'});
}).catch(err=>{
console.error(err) console.error(err)
}) })
} }