Vue3项目自学训练----音乐播放器(使用vueX管理项目)(十二)

目录

 一、封装对话弹窗组件(封装组件要把所有的功能封装在组件内)

二、设置列表打开关闭动画(一个同级元素用transition包裹)

三、全屏播放器和mini播放器的显示切换

四、点击全屏播放器的缩小按钮变成mini播放器

五、mini播放器切换成全屏播放器

六、打开全屏播放器的动画效果

七、优化更新列表滚动置顶

八、推荐歌单首页点击进入的数据渲染


 一、封装对话弹窗组件(封装组件要把所有的功能封装在组件内)

1、绑定点击事件

<span class="clear" @click.stop="showConfirm">
// 清空列表唤起弹窗;
function showConfirm() {
  console.log(1);
}

2、先新建弹窗组件Confirm.vue完成样式结构

<template>
  <teleport to="body">
    <!-- 定位到body的位置 -->
    <transition name="confirm-fade">
      <div class="confirm" v-show="visible">
        <div class="confirm-wrapper">
          <div class="confirm-content">
            <p class="text">外部传入提示信息</p>
            <div class="operate">
              <div class="operate-btn left">外部传入选项内容A</div>
              <div class="operate-btn">外部传入选项内容B</div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </teleport>
</template>

<script setup>
</script>

<style lang="scss" scoped>
.confirm {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 998;
  background-color: $color-background-d;
  &.confirm-fade-enter-active {
    animation: confirm-fadein 0.3s;
    .confirm-content {
      animation: confirm-zoom-in 0.3s;
    }
  }
  &.confirm-fade-leave-active {
    animation: confirm-fadeout 0.3s;
    .confirm-content {
      animation: confirm-zoom-out 0.3s;
    }
  }
  .confirm-wrapper {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 999;
    .confirm-content {
      width: 270px;
      border-radius: 13px;
      background: $color-highlight-background;
      .text {
        padding: 19px 15px;
        line-height: 22px;
        text-align: center;
        font-size: $font-size-large;
        color: $color-text-l;
      }
      .operate {
        display: flex;
        align-items: center;
        text-align: center;
        font-size: $font-size-large;
        .operate-btn {
          flex: 1;
          line-height: 22px;
          padding: 10px 0;
          border-top: 1px solid $color-background-d;
          color: $color-text-l;
          &.left {
            border-right: 1px solid $color-background-d;
            color: $color-text;
          }
        }
      }
    }
  }
}

@keyframes confirm-fadein {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@keyframes confirm-fadeout {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

@keyframes confirm-zoom-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.1);
  }
  100% {
    transform: scale(1);
  }
}

@keyframes confirm-zoom-out {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(0);
  }
}
</style>

3、操作组件本身的功能封装

<template>
  <teleport to="body">
    <!-- 定位到body的位置 -->
    <transition name="confirm-fade">
      <div class="confirm" v-show="visible" @click="hide()">
        <div class="confirm-wrapper" @click.stop>
          <div class="confirm-content">
            <p class="text">{{ text }}</p>
            <div class="operate">
              <div class="operate-btn left" @click="confirm">{{ confirmBtnText }}</div>
              <div class="operate-btn" @click="cancel">{{ cancelBtnText }}</div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </teleport>
</template>

<script setup>
import { ref } from "vue";

const props = defineProps({
  // 弹窗提示信息
  text: {
    type: String,
    default: "是否删除",
  },
  confirmBtnText: {
    type: String,
    default: "确定",
  },
  cancelBtnText: {
    type: String,
    default: "取消",
  },
});
// 创建自定义事件
const emit = defineEmits(["confirm", "cancel"])
const visible = ref(false);
// 显示
function show() {
    visible.value = true;
}
// 隐藏
function hide() {
    visible.value = false
}
// 点击确定时
function confirm() {
    emit("confirm");
    hide();
}
// 点击取消时
function cancel() {
    emit("cancel");
    hide();
}

// 把组件内部的函数暴露出去给其他组件
defineExpose({
    show,
})
</script>

4、使用封装组件

<my-confirm
      text="是否清空播放列表"
      confirmBtnText="清空"
      ref="confirmRef"
      @confirm="clearAll"
    ></my-confirm>



<script setup>
import MyConfirm from "@/components/base/Confirm.vue";

// 定义ref
const confirmRef = ref(null);

// 清空列表唤起弹窗;
function showConfirm() {
  confirmRef.value.show();
}
function clearAll() {
  store.dispatch("clearSongList");
}
// 暴露出去
defineExpose({
  show,
});
</script>

二、设置列表打开关闭动画(一个同级元素用transition包裹)

<transition name="list-fade">
    <div class="playlist" v-show="visible" @click.stop="hide">
    </div>
  </transition>

<style scoped lang="scss">
&.list-fade-enter-active,
  &.list-fade-leave-active {
    transition: opacity 0.3s;
    .list-wrapper {
      transition: all 0.3s;
    }
  }
  &.list-fade-enter-from,
  &.list-fade-leave-to {
    opacity: 0;
    .list-wrapper {
      transform: translate(0, 100%);
    }
  }
<style>

三、全屏播放器和mini播放器的显示切换

// 拿到vuex的fullScreen
const fullScreen = computed(() => store.state.fullScreen);
<!-- mini:true / 全屏:false -->
<div class="mini-player" v-show="!fullScreen"></div>
<div class="normal-player" v-show="fullScreen"></div>

四、点击全屏播放器的缩小按钮变成mini播放器

<div class="back" @click="showMiniPlayer"></div>

<script setup>
// 打开mini播放器
function showMiniPlayer() {
  store.commit('setFullscreen', false);
}
<script>

五、mini播放器切换成全屏播放器

<div class="mini-player" v-show="!fullScreen" @click="showFullScreenPlayer"></div>

<script setup>
// 打开全屏播放器
function showFullScreenPlayer() {
  store.commit("setFullScreen", true);
}
<script>

六、打开全屏播放器的动画效果

    <transition name="normal">
      <div class="normal-player" v-show="fullScreen"></div>
</transition>


<style scoped>

&.normal-enter-active,
    &.normal-leave-active {
      transition: all 0.6s;
      .middle-l,
      .top,
      .bottom {
        transition: all 0.6s cubic-bezier(0.45, 0, 0.55, 1);
      }
    }
    &.normal-enter-from,
    &.normal-leave-to {
      opacity: 0;
      .top {
        transform: translate3d(0, -100px, 0);
      }
      .bottom {
        transform: translate3d(0, 100px, 0);
      }
      .middle-l {
        transform: scale(0);
      }
    }
</style>

七、优化更新列表滚动置顶

// 组件的显示和隐藏
async function show() {
  visible.value = true;
  // 更新一下滚动效果,等DOM更新之后在更新滚动组件
  await nextTick()
  scrollRef.value.scroll.refresh();
  scrollToCurrent();
}

八、推荐歌单首页点击进入的数据渲染

在视图View文件夹下创建AlumDetail.vue,配置路由

          <ul>
            <li
              @click="selectItem(item)"
              class="recommend-item"
              v-for="(item, index) in albums"
              :key="item.id"
            >
            </li>
          </ul>
<scipt>
function selectItem(item) {
  //拿到数据的id
  console.log(item.id);
}
</script>

获取数据接口(axios)

//获取歌单详情
export function getAlbum(album) {
    return get('/playlist/detail', {
        id: album.id
    })
}
import { getAlbum } from "@/service/recommend";

九、vue3封装方法并使用

封装到一个js文件中

import { computed, onMounted, ref } from "@vue/runtime-core";
import { useRoute, useRouter } from "vue-router";
import myStorage from "@/assets/js/storage-api";

export default function createDetail(props,sessionKey, getApi) {
    const route = useRoute();
    const router = useRouter();
    // 歌曲列表
    const songs = ref([]);
    const loading = ref(true);

    const computedData = computed(() => {
        let result = null;
        const data = props.detailObj;
        if (data.id) {
            // props获取成功
            result = data;
        } else {
            //props获取失败
            const cached = myStorage.getLocal("sessionKey");
            // 存储的值cached存在的话,并且cached的id和路由上的id保持一致
            if (cached && cached.id === route.params.id / 1) {
                result = cached;
            }
        }
        return result;
    });
    // 背景图片
    const picUrl = computed(() => {
        return computedData.value ? computedData.value.picUrl : "";
    });
    // 标题
    const listTitle = computed(() => {
        return computedData.value ? computedData.value.name : "";
    });

    onMounted(async () => {
        const data = computedData.value;
        //props内和本地存储内都没有数据
        if (!data) {
            // 跳转回当前路由的上一级路由
            router.push({ path: route.matched[0].path });
            return;
        }

        const result = await getApi(data);
        songs.value = result.hotSongs;
        loading.value = false;
    });

    return {
        picUrl,
        listTitle,
        songs,
        loading
    }
}

然后导入使用

<template>
  <div class="singer-detail">
    <my-musicList
      :picUrl="picUrl"
      :listTitle="listTitle"
      :songs="songs"
      :loading="loading"
    ></my-musicList>
  </div>
</template>

<script setup>
import MyMusicList from "@/components/MusicList";
import { getSingerDetail } from "@/service/singer";

import createDetail from "@/assets/js/create-detail";
// 接收数据
const props = defineProps(["detailObj"]);

const { picUrl, listTitle, songs, loading } = createDetail(
  props,
  "__singerDetail__",
  getSingerDetail
);
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.MUXIAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值