Merge branch 'master' of git.flexites.org:Students/ITRadio

This commit is contained in:
Mike0001-droid 2024-06-13 11:20:14 +05:00
commit 4348f184eb
18 changed files with 307 additions and 177 deletions

View File

@ -1,15 +1,15 @@
<template>
<div class="app">
<AppHeader :showAuthentication="showAuthentication" @shopAuthentication="changeShowAuthentication"/>
<AppHeader/>
<div class="app__block">
<Page404 v-if="showErrorPage404"/>
<Page500 v-if="showErrorPage500"/>
<routerView v-else/>
</div>
<Player @shopAuthentication="changeShowAuthentication"/>
<Player/>
<AppFooter/>
</div>
<Authentication :showModal="showAuthentication" @hideModal="changeShowAuthentication"/>
<Authentication/>
</template>
<script>
@ -33,7 +33,6 @@ export default {
},
data() {
return {
showAuthentication: false,
showSidebarBlock: false,
};
},
@ -56,9 +55,6 @@ export default {
mounted() {
},
methods: {
changeShowAuthentication(value) {
this.showAuthentication = value;
}
}
};
</script>

View File

@ -1,5 +1,5 @@
.footer{
padding: 0 0 var(--space-between-block);
padding-bottom: var(--space-between-block);
width: 100%;
&__top{
padding-bottom: 80px;

View File

@ -29,6 +29,10 @@
&__content{
max-width: var(--container);
margin: 0 auto;
padding: 0 50px;
@mixin responsive-l {
padding: 0 20px;
}
.m--indentation{
}
}

View File

@ -51,7 +51,12 @@
&__subtitle {
text-align: center;
max-width: 600px;
margin: 0 auto;
@mixin responsive-l {
padding: 0 20px;
}
span {
font-weight: 700;
}
@ -105,26 +110,31 @@
max-height: var(--base-content-size);
background-size: contain;
scale: 0.8;
margin-left: calc(-1920px + 100vw);
margin-left: calc((100vw + -1 * 1920px) / 2);
}
&__description {
margin-top: calc(var(--space-between-sections) / 2);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: var(--space-between-sections);
@mixin responsive-m {
gap: var(--space-between-block);
}
}
&__info {
width: 60%;
@mixin responsive-m {
width: 100%;
}
&--item {
display: flex;
justify-content: space-between;
color: transparent;
background-clip: text;
background-image: var(--linear-gradient);
&.m--circle {
align-items: end;
@ -141,20 +151,32 @@
font-weight: 400;
color: var(--color-white);
width: 57%;
@mixin responsive-m {
width: 75%;
}
}
}
}
&__content {
max-width: 360px;
@mixin responsive-m {
display: flex;
align-items: flex-start;
gap: calc(var(--space-between-block) / 2);
max-width: none;
}
&--item {
display: flex;
flex-direction: column;
align-items: flex-start;
font-size: 16px;
margin-bottom: 2.5rem;
position: relative;
z-index: 2;
@mixin responsive-m {
width: calc(100% / 3);
}
&:before {
content: '';
height: 50px;

View File

@ -8,6 +8,8 @@
background-image: var(--linear-gradient);
margin: 0.75rem 0;
gap: 1rem;
position: relative;
z-index: 2;
.spinner {
width: 48px;
height: 48px;

View File

@ -5,7 +5,7 @@
gap: 0 var(--space-between-block);
}
&-item{
width: calc(50% - 40px / 2);
width: calc(50% - var(--space-between-block) / 2);
position: relative;
display: flex;
align-items: center;

View File

@ -73,6 +73,7 @@ export default boot(async ({ app, router }) => {
}),
toggle: createInput(toggle, {
props: ['placeholder', 'disabled', 'readonly'],
emits: ['toggle']
}),
},
};

View File

@ -5,12 +5,12 @@
<img src="@/assets/img/icon/logo.svg" alt="logo"/>
</div>
<ul class="header__menu" :class="showMenu&&'m--active'">
<div class="header__burger m--active m--menu" @click="handlerShowMenu">
<li class="header__burger m--active m--menu" @click="handlerShowMenu">
<span></span>
</div>
<div class="header__logo m--menu" @click="next('home')">
</li>
<li class="header__logo m--menu" @click="next('home')">
<img src="@/assets/img/icon/logo.svg" alt="logo"/>
</div>
</li>
<li
v-for="(item, key) in menu"
:key="key"
@ -38,14 +38,7 @@
<script>
export default {
name: 'app-header',
props:{
showAuthentication:{
type: Boolean,
default(){
return false
}
}
},
props:{},
data(){
return{
menuList:[
@ -117,12 +110,15 @@ export default {
if (item.role==='login' && !this.user?.id) return !this.user?.id
return false
})
}
},
showAuthentication(){
return this.$store.state.showAuthModal
},
},
methods:{
handlerClick(methods){
if (methods==='login'){
this.$emit('shopAuthentication', true)
this.$store.dispatch('setShowAuthModal', true);
}
if (methods==='profile'){
this.next('profile')

View File

@ -6,11 +6,11 @@
content-transition="vfm-fade"
overlay-transition="vfm-fade"
:clickToClose="false"
@click-outside="$emit('hideModal')"
@click-outside="close()"
>
<button
class="button modal__close"
@click="$emit('hideModal')"
@click="close"
>
</button>
<div class="authentication">
@ -40,7 +40,7 @@
outerClass: '$reset',
}"
@submit="submitHandler"
@click-outside="$emit('hideModal')"
@click-outside="close"
>
<FormKitSchema :schema="loginForm"/>
</FormKit>
@ -73,14 +73,6 @@ import {app} from "@/services";
export default {
name: 'authentication',
props: {
showModal: {
type: Boolean,
default() {
return false;
}
},
},
data() {
return {
currentTabsItem: 'login',
@ -144,9 +136,9 @@ export default {
}
},
computed: {
show() {
return this.showModal;
},
show(){
return this.$store.state.showAuthModal
}
},
methods: {
changeTab(tab) {
@ -158,8 +150,8 @@ export default {
this.$store.dispatch('setToken', res);
app.user().then(user=>{
this.$store.dispatch('setUser', user);
this.close();
this.next('profile');
this.$emit('hideModal');
}).catch(err=>{
console.log(err)
})
@ -171,8 +163,8 @@ export default {
this.$store.dispatch('setToken', res);
app.user().then(user=>{
this.$store.dispatch('setUser', user);
this.close();
this.next('profile');
this.$emit('hideModal');
}).catch(err=>{
console.log(err)
})
@ -183,7 +175,7 @@ export default {
},
close() {
this.$emit('hidden', false);
this.$store.dispatch('setShowAuthModal', false);
},
next(name){
this.$router.push({name});

View File

@ -1,27 +1,42 @@
<template>
<input
class="input field__checkbox-input"
:id="props.context.id"
:value="props.context._value"
:id="context.id"
:checked="context._value"
:disabled="disabled"
:readonly="readonly"
type="checkbox"
@change="change"
>
<label class="field__checkbox-label" :for="props.context.id">
{{ props.context.label }}
<label class="field__checkbox-label" :for="context.id">
{{ context.label }}
</label>
</template>
<script setup>
import { defineProps, ref, onUpdated, computed } from 'vue';
<script>
import {defineProps, ref, onUpdated, computed} from 'vue';
const props = defineProps({
context: {
type: Object,
default: () => {}
export default {
props: {
context: {
type: Object,
default: () => {
}
}
},
data() {
return {
placeholder: this.context.placeholder || '',
disabled: this.context.disabled || false,
readonly: this.context.readonly || false,
}
},
methods: {
change() {
this.context.node.input(!this.context._value);
}
}
});
const placeholder = props.context.placeholder || '';
const disabled = props.context.disabled || false;
const readonly = props.context.readonly || false;
}
</script>

View File

@ -5,21 +5,22 @@
</div>
<div class="player__content">
<div class="player__top">
<button v-if="isPlay" @click="handlerPause" class="button player__btn m--pause">
<button v-if="currentPlay.isPlay" @click="handlerPause" class="button player__btn m--pause">
</button>
<button v-else @click="handlerPlay" class="button player__btn m--play">
</button>
<div class="player__executor">
{{currentPlay.title || '—'}}
<span>{{currentPlay.artist || '—'}}</span>
{{ currentPlay.title || '—' }}
<span>{{ currentPlay.artist || '—' }}</span>
</div>
<div class="player__favorites" :class="[isFavorites&&'m--active']" @click="handlerFavorites">
</div>
<div class="player__tools">
<FormKit
v-model="isPlayRadio"
v-model="isLive"
type="toggle"
label="Включить мою музыку"
@change="changeLive"
/>
<div class="player__volume">
<span @click="setVolume"/>
@ -40,102 +41,107 @@
</template>
<script>
import {audio as Player} from "@/services";
import {app} from "@/services";
import {audio as Player, app} from "@/services";
export default {
name: 'player',
data() {
return {
audioUrl: 'http://82.97.242.49:18000/radio.mp3',
isPlay: false,
isFavorites: false,
isPlayRadio: true,
connection: null,
currentPlay: {},
player: {
target: null,
volume: 50,
progress: 100,
live: false,
}
}
connection: null,
isLive: !this.$store.state.currentPlay.live,
}
},
created() {
this.connectionPlayer();
},
mounted() {
this.initPlayer();
// this.$refs.player.addEventListener('timeupdate', this.updateProgress);
computed: {
user() {
return this.$store.state.user;
},
currentPlay() {
return this.$store.state.currentPlay
},
player() {
return this.$store.state.player
}
},
watch: {
'currentPlay': {
immediate: false,
handler() {
if (this.isLive!==!this.currentPlay.live){
this.isLive = !this.currentPlay.live;
}
},
},
},
created() {
this.connectionPlayer();
},
mounted() {
if (!this.player.target) {
this.$store.dispatch('initPlayer');
}
},
computed:{
user() {
return this.$store.state.user;
},
},
methods: {
initPlayer(){
this.player.target = document.createElement('audio')
this.player.target.src = '';
this.player.target.preload = 'auto';
this.player.target.controls = true
},
connectionPlayer() {
if (this.connection) {
this.connection.removePlay();
}
this.connection = new Player();
this.connection.init();
this.connection.onHandler(this.getPlaying);
},
getPlaying (e){
const jsonData = JSON.parse(e.data)
if (jsonData?.pub?.data){
const data = jsonData?.pub?.data;
if (data.np.station.listen_url!==this.player.target.src){
this.player.target.src = data.np.station.listen_url;
this.player.live = true;
}
this.currentPlay = data.np.now_playing.song;
}
},
updateProgress(){
connectionPlayer() {
if (this.connection) {
this.connection.removePlay();
}
this.connection = new Player();
this.connection.init();
this.connection.onHandler(this.getPlaying);
},
getPlaying(e) {
const jsonData = JSON.parse(e.data)
if (jsonData?.pub?.data) {
const data = jsonData?.pub?.data;
if (this.currentPlay.live) {
if (data.np.station.listen_url !== this.player.target.src) {
this.$store.dispatch('changePlayer', data.np.station.listen_url);
}
this.$store.dispatch('setCurrentPlay', {...data.np.now_playing.song, live: true});
}
}
},
updateProgress() {
console.log(this.$refs.player.currentTime)
console.log(this.$refs.player.duration)
},
handlerPlay() {
if (this.player.target?.play){
this.player.target.play();
this.isPlay = true;
}
this.$store.dispatch('handlerPlayer', {play: true});
},
handlerPause() {
console.log(this.player.target?.pause)
if (this.player.target.src) {
this.player.target.pause();
this.isPlay = false;
}
this.$store.dispatch('handlerPlayer', {pause: true});
},
handlerFavorites(){
if (this.user?.id){
this.isFavorites = !this.isFavorites;
const params = {...this.currentPlay, azura_id:this.currentPlay.id};
app.createFavoriteForUser(params).then(()=>{
handlerFavorites() {
if (this.user?.id) {
this.isFavorites = !this.isFavorites;
const params = {...this.currentPlay, azura_id: this.currentPlay.id};
app.createFavoriteForUser(params).then(() => {
})
} else {
this.$emit('shopAuthentication', true)
}
})
}else {
this.$emit('shopAuthentication', true)
}
},
setVolume(){
this.player.volume = this.player.volume === 0? 50: 0;
this.player.target.volume = this.player.volume / 100;
setVolume() {
this.$store.dispatch('handlerPlayer', {volume: 100});
},
changeVolume() {
this.player.target.volume = this.player.volume / 100;
this.$store.dispatch('handlerPlayer', {volume: 100});
},
changeLive(e) {
console.log(e)
if (this.currentPlay.live) {
this.$store.dispatch('setCurrentPlay', {live: false});
console.log('избранное')
} else {
this.$store.dispatch('setCurrentPlay', {live: true});
console.log('поток')
}
}
}
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="song-item" :class="[selectSong===song&&'m--select']" @click="handlerSelectSong">
<div class="song-item" :class="[currentPlay.id===song.id&&'m--select']" @click="handlerSelectSong">
<div class="song-item__selected" :class="[!isPlay&&'m--stop']">
<svg width="33" height="28" viewBox="0 0 33 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="" d="M0 15C0 13.6193 1.11929 12.5 2.5 12.5C3.88071 12.5 5 13.6193 5 15V27.5H0V15Z" fill="#7138F4"/>
@ -16,8 +16,8 @@
</button>
</div>
<div class="song-item__info">
<span>Название трека</span>
Исполнитель
<span>{{song.title}}</span>
{{song.artist}}
</div>
<div class="song-item__tools">
<button class="button song-item__btn m--small m--favorites"></button>
@ -37,9 +37,9 @@ export default {
}
},
song:{
type: Number,
type: Object,
default(){
return null
return {}
}
},
playSong:{
@ -62,6 +62,11 @@ export default {
isPlay: this.playSong
}
},
computed:{
currentPlay(){
return this.$store.state.currentPlay
}
},
methods:{
handlerPlay() {
this.isPlay = true;
@ -70,7 +75,7 @@ export default {
this.isPlay = false;
},
handlerSelectSong(){
this.$emit('changeSelect', this.song)
this.$store.dispatch('setCurrentPlay', {...this.song, live: false});
}
}
}

View File

@ -1,12 +1,9 @@
<template>
<div class="song-list">
<SongItem
v-for="i in 10"
:key="i"
:song="i"
:playSong="i===selectSong"
:selectSong="selectSong"
@changeSelect="changeSelectSong"
v-for="song in songList"
:key="song"
:song="song"
/>
</div>
</template>
@ -17,15 +14,15 @@ import SongItem from "@/components/song-item.vue";
export default {
name: 'song-list',
components: {SongItem},
data(){
return{
selectSong: null
props: {
songList: {
type: Array,
default: () => []
}
},
methods:{
changeSelectSong(id){
this.selectSong = id;
}
}
data() {
return {}
},
methods: {}
}
</script>

View File

@ -27,6 +27,34 @@ export default class extends REST {
});
}
static getTeams(station, params) {
return this._get(`radio/teams`, params, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении команды');
});
}
static createFavoriteForUser(params){
return this._post(`radio/song/add_favorite`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getFavoriteList(params){
return this._get(`radio/song`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getNews(station, params) {
return this._get(`radio.mp3`, params, {}).then((data) => {
return data;
@ -41,13 +69,7 @@ export default class extends REST {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getTeams(station, params) {
return this._get(`radio/teams`, params, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении команды');
});
}
static getRubriks(station, params) {
return this._get(`radio.mp3`, params, {}).then((data) => {
return data;
@ -55,11 +77,4 @@ export default class extends REST {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static createFavoriteForUser(params){
return this._post(`radio/song/create_song`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
}

View File

@ -20,7 +20,7 @@ class RESTError extends Error {
//if (this.response && (this.response.status === 401 || this.response.status === 403)) {
if (this.response && (this.response.status === 401)) {
store.dispatch('deathUser');
router.push({ name: 'auth' });
store.dispatch('setShowAuthModal', true);
} else {
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);

View File

@ -2,7 +2,7 @@ import { createStore } from 'vuex'
import VuexPersist from 'vuex-persist';
const vuexPersist = new VuexPersist({
key: 'tugan-v-cirk-ne-hodim',
key: 'it-radio',
//storage: window.localStorage
});
@ -14,8 +14,15 @@ export default createStore({
token: null,
refreshToken: null,
user: null,
showAuthModal: false,
station: {
id: 1
},
currentPlay: {},
player:{
target: null,
volume: 50,
live: true,
}
}
},
@ -32,6 +39,38 @@ export default createStore({
state.token = null;
state.refreshToken = null;
},
setCurrentPlay(state, song){
state.currentPlay = song;
},
setShowAuthModal(state, show){
state.showAuthModal = show
},
setPlayer(state, params){
state.player = {...state.player,...params}
},
initPlayer(state){
state.player.target = document.createElement('audio')
state.player.target.src = '';
state.player.target.preload = 'auto';
state.player.target.controls = true;
console.log('initPlayer',state.player.target)
},
changePlayer(state, params){
state.player.target.src = params;
},
handlerPlayer(state, params){
if (params.pause){
state.currentPlay.isPlay = false;
state.player.target.pause();
}
if (params.play){
state.currentPlay.isPlay = true;
state.player.target.play();
}
if (params.volume){
state.player.target.volume = params.volume;
}
},
},
actions: {
setToken(context, tokens) {
@ -44,5 +83,24 @@ export default createStore({
context.commit('user', {});
context.commit('removeToken');
},
setCurrentPlay(context, song){
context.commit('setCurrentPlay', song);
},
setShowAuthModal(context, show){
context.commit('setShowAuthModal', show);
},
setPlayer(context, params){
context.commit('setPlayer', params);
},
initPlayer(context){
context.commit('initPlayer');
},
handlerPlayer(context, params){
context.commit('handlerPlayer', params);
},
changePlayer(context, params){
context.commit('changePlayer', params);
}
}
});

View File

@ -3,9 +3,9 @@
<div class="home__meaning" ref="targetWrapper">
<div class="app__content">
<p class="text home__subtitle">
Мы цифровое <span>онлайн радио.</span>
Помогаем разобраться в том, что такое <span>IT.</span>
Находимся в <span>Челябинске</span>, но вещаем на весь <span>Мир</span>
Мы цифровое <span>онлайн радио.</span><br/>
Помогаем разобраться в том, что такое <span>IT.</span><br/>
Находимся в <span>Челябинске</span>, но вещаем на весь <span>Мир</span><br/>
</p>
<h1 class="home__title" ref="targetTitle">
IT-Радио <span id="targetTitleSpan">радиостанция про сферу</span> технологий <br/> и развитие <span id="targetTitleSpan">в IT</span>
@ -19,7 +19,7 @@
<div class="home__banner" ref="target">
</div>
</div>
<template v-if="false">
<template v-if="true">
<div class="app__content">
<div class="home__description">

View File

@ -20,7 +20,12 @@
</button>
</div>
<template v-if="currentTabsItem==='music'">
<SongList/>
<template v-if="showLoader">
<div class="loader">
<div class="spinner" /> Загрузка данных
</div>
</template>
<SongList v-else :songList="songList"/>
</template>
</div>
</template>
@ -28,6 +33,7 @@
<script>
import AppBreadcrumbs from "@/components/app-breadcrumbs.vue";
import SongList from "@/components/song-list.vue";
import {app} from "@/services";
export default {
name: 'profile',
@ -49,6 +55,8 @@ export default {
name: 'playlists'
},
],
songList:[],
showLoader:true,
}
},
watch: {
@ -63,7 +71,20 @@ export default {
},
}
},
created() {
this.getSongList();
},
methods: {
getSongList(){
this.showLoader = true;
app.getFavoriteList().then(res=>{
this.showLoader = false;
this.songList = res;
}).catch(err=>{
this.showLoader = false;
console.error(err)
})
},
changeTab(tab) {
this.currentTabsItem = tab;
this.$router.push({name: this.$route.name, hash: `#${tab}`});