• 仿网易云移动端项目Vue3.2+Pinia+Vant+axios


    仿网易云移动端项目Vue3.2+Pinia+Vant+axios

    目录

    仿网易云移动端项目Vue3.2+Pinia+Vant+axios

    前期准备(Pinia,rem,初始化样式,图标引入,vant组件,axios)

    安装pinia

    1.在main.js引入注册

    2.创建store

     rem移动适配

    1.创建rem.js实现移动适配布局

    2.在index.html引入

    3.可以用px to rem 插件进行px和rem转换

    阿里图标引入(Symbol方法)

    1.在官网添加项目后复制symbol代码在index.html引入

    2.使用svg

    3.初始化样式,设置图标大小(在App.vue)

    使用vant组件库

    1.安装vant3组件

    2.引入vant(按需引入,安装插件)

    3.测试按钮(在main.js进行注册)

    4.对引入vant组件库进行集中管理(封装)

    安装axios

    1.对axios进行封装

    2.在组件中调用方法请求数据

    组件开发(知识点)

    路由

    1.独享路由守卫

    2.全局前置守卫

    首页

    1.在组件全局编程式路由跳转(无需引入)

    2.flex布局

    3.懒加载的轮播图组件(v-for循环请求回来的图片)

    4.用vue2和vue3的写法获取数据

    5.组件的模板内使用router-link(路由传参)

    歌单详情页面

    2.在刷新时读取不到store的数据,则先存储到本地

    3.对播放量处理(定义一个函数对渲染的数据进行处理返回出去)

    4.Css对图片进行虚化,并将其层级放底层(要定位)

    5.Css文本超出几行就进行省略号表示

    6.用v-for渲染数组时,数组内还有数组的元素可以再v-for进行渲染

    底部组件FooterMusic.vue

    1.vue2中的this.$ref在Vue3的使用

    2.定时器的使用和清除

    3.模板字符串的使用

    歌词和磁盘播放页面

     1.旋转图片动画(活跃和不活跃)

    2.歌词

    3.计算属性歌词处理(计算属性在vue3.2的使用)

    4.使用vue3marquee实现歌名走马灯效果

    5.watch在vue3.2的使用

    搜索页面

     1.页面渲染时读取本地历史记录

    2.回车搜索需要进行去重和数组追加(unshift和Set语法)

    登录组件

     1.登录的接口

    2.登录需要的数据共享

    3.登录路由组件


    vue3.2 + vue-router + pinia + vant+axios

    基于vue-cli创建,rem移动适配方案

    前期准备(Pinia,rem,初始化样式,图标引入,vant组件,axios)

    安装pinia

    npm install pinia -S

    1.在main.js引入注册

    1. import { createApp } from 'vue'
    2. import App from './App.vue'
    3. import router from './router'
    4. import { createPinia } from 'pinia'
    5. const app = createApp(App)
    6. app.use(router).use(createPinia()).mount('#app')

    2.创建store

    1. import { defineStore } from 'pinia'
    2. export const useStore = defineStore('main', {
    3. state: () => ({
    4. }),
    5. getters: {
    6. },
    7. actions: {
    8. }
    9. })

     rem移动适配

    1.创建rem.js实现移动适配布局

    在public存放静态资源新建js文件夹,在js文件夹创建rem.js实现移动适配布局:

    1. function remSize () {
    2. /* 获取设备宽度 */
    3. let deviceWith = document.documentElement.clientWidth || window.innerWidth
    4. /* 设计稿宽度 */
    5. if (deviceWith >= 750) {
    6. deviceWith = 750
    7. }
    8. if (deviceWith <= 320) {
    9. deviceWith = 320
    10. }
    11. /* 设置rem,
    12. 750px--> 1rem=100px
    13. 375px--> 1rem=50px
    14. */
    15. document.documentElement.style.fontSize = (deviceWith / 7.5) + 'px'
    16. /* 设置字体大小 */
    17. document.querySelector('body').style.fontSize = 0.3 + 'rem'
    18. }
    19. remSize()
    20. /* 当窗口发生变化 */
    21. window.onresize = function () {
    22. remSize()
    23. }

    2.在index.html引入

    1. <div id="app"></div>
    2. <!-- 引入rem.js,<%= BASE_URL %>这个为基础路径 -->
    3. <script src="<%= BASE_URL %>js/rem.js"></script>

    3.可以用px to rem 插件进行px和rem转换

    需要对插件进行配置,基准font-size这里设置为50

    那就是1rem=50px

    阿里图标引入(Symbol方法)

    1.在官网添加项目后复制symbol代码在index.html引入

    1. <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    2. <!-- 引入阿里图标 -->
    3. <script src="//at.alicdn.com/t/font_3415587_mpuudaajazg.js"></script>

    2.使用svg

    在官网:https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=3415587

    查看使用帮助,这里用到的是symbol

    组件内使用:

    1. <svg class="icon" aria-hidden="true">
    2. <use xlink:href="#icon-liebiao2"></use>
    3. </svg>

    注意:类名要加#号

    3.初始化样式,设置图标大小(在App.vue)

    symbol是设置width和height,而font class是设置fontsize

    1. * {
    2. margin: 0;
    3. padding: 0;
    4. box-sizing: border-box;
    5. }
    6. .icon {
    7. width: .4rem;
    8. height: .4rem;
    9. }
    10. a{
    11. color: black;
    12. }

    使用vant组件库

    官网:https://vant-contrib.gitee.io/vant/#/zh-CN

    1.安装vant3组件

    npm i vant

    2.引入vant(按需引入,安装插件)

    参照官网步骤

    npm i babel-plugin-import -D

    在.babelrc 或 babel.config.js 中添加配置

    3.测试按钮(在main.js进行注册)

    1. import { Button } from 'vant';
    2. app.use(Button);

    4.对引入vant组件库进行集中管理(封装)

    在src目录创建plugins文件夹,在plugins文件夹创建index.js:

    1. import { Swipe, SwipeItem, NavBar, Button, Search, Popup } from 'vant'
    2. /* 放入数组中 */
    3. const plugins = [
    4. Swipe, NavBar, SwipeItem, Button, Popup, Search
    5. ]
    6. /* 循环将每一个插件注册到app上,main.js直接调用这个方法即可 */
    7. export default function getVant (app) {
    8. plugins.forEach((item) => {
    9. return app.use(item)
    10. })
    11. }

     在main.js引入调用方法即可

    1. import { createApp } from 'vue'
    2. import App from './App.vue'
    3. import router from './router'
    4. import { createPinia } from 'pinia'
    5. /* 引入插件 */
    6. import getVant from './plugins'
    7. const app = createApp(App)
    8. getVant(app)
    9. app.use(router).use(createPinia()).mount('#app')

    安装axios

    npm i axios -S

    1.对axios进行封装

    创建utils文件夹,在其文件夹下创建request.js用于封装请求根路径:

    1. import axios from 'axios'
    2. const service = axios.create({
    3. baseURL: 'http://localhost:3000',
    4. timeout: 3000
    5. })
    6. export default service

    创建api文件夹,在其文件夹下创建homeApi.js模块对关于首页api接口进行封装:

    1. import service from '@/utils/request.js'
    2. /* 获取首页轮播图数据 */
    3. /* 第一种写法
    4. export function getBanner () {
    5. return service({
    6. method: 'GET',
    7. url: '/banner?type=2'
    8. })
    9. } */
    10. // 第二种写法
    11. export function getBanner (type) {
    12. return service.get('/banner', {
    13. /* 请求参数 */
    14. params: {
    15. type
    16. }
    17. })
    18. }

    2.在组件中调用方法请求数据

    1. <script setup >
    2. import { toRefs, reactive, onMounted } from 'vue'
    3. import { getBanner } from '@/api/homeApi.js'
    4. const state = reactive({
    5. images: [
    6. 'https://c99=1533&h=575&f=webp&q=90',
    7. 'https://cdn.cnbj1.fds.api.mi-img.c=90'
    8. ]
    9. })
    10. onMounted(async () => {
    11. /* axios.get('http://localhost:3000/banner?type=2').then((res) => {
    12. console.log(res)
    13. state.images = res.data.banners
    14. console.log(state.images)
    15. }) */
    16. const res = await getBanner(2)
    17. state.images = res.data.banners
    18. })
    19. const { images } = toRefs(state)
    20. </script>

    组件开发(知识点)

    路由

    1.独享路由守卫

    判断进入个人中心页面是否有登录或者是否有token,如果没有就跳转到登录页面

    1. {
    2. path: '/infoUser',
    3. name: 'InfoUser',
    4. // 独享路由守卫
    5. beforeEnter: (to, from, next) => {
    6. // pinia要在这里定义
    7. const store = FooterMusicStore()
    8. if (store.isLogin || store.token || localStorage.getItem('token')) {
    9. next()
    10. } else {
    11. next('/login')
    12. }
    13. },
    14. component: () => import('../views/InfoUser.vue')
    15. }

    2.全局前置守卫

    如果跳转到登录页面就将隐藏底部播放栏组件

    1. const router = createRouter({
    2. history: createWebHashHistory(),
    3. routes
    4. })
    5. // 全局前置守卫
    6. router.beforeEach((to, from) => {
    7. const store = FooterMusicStore()
    8. if (to.path === '/login') {
    9. store.isFooterMusic = false
    10. } else {
    11. store.isFooterMusic = true
    12. }
    13. })
    14. export default router

    首页

    路由组件:HomeView

    组件:

     

    1.在组件全局编程式路由跳转(无需引入)

    点击我的就跳转到个人中心页面

    $router.push('路径')

    1. <div class="topContent">
    2. <span @click="$router.push('/infoUser')">我的</span>
    3. <span class="active">发现</span>
    4. <span>云村</span>
    5. <span>视频</span>
    6. </div>

    2.flex布局

    1. display: flex;
    2. //设置盒子距离
    3. justify-content: space-between;
    4. //垂直居中
    5. align-items: center;
    6. /* 修改主轴对齐方向 */
    7. flex-direction: column;

    3.懒加载的轮播图组件(v-for循环请求回来的图片)

    1. <script setup >
    2. import { toRefs, reactive, onMounted } from 'vue'
    3. import { getBanner } from '@/api/homeApi.js'
    4. const state = reactive({
    5. images: [
    6. ]
    7. })
    8. onMounted(async () => {
    9. const res = await getBanner(2)
    10. state.images = res.data.banners
    11. })
    12. const { images } = toRefs(state)
    13. </script>
    14. <template>
    15. <div id="swiperTop">
    16. <!-- 懒加载 -->
    17. <van-swipe :autoplay="3000" lazy-render>
    18. <van-swipe-item v-for="image in images" :key="image">
    19. <img :src="image.pic" />
    20. </van-swipe-item>
    21. </van-swipe>
    22. </div>
    23. </template>

    4.用vue2和vue3的写法获取数据

    vue2

    1. export default {
    2. data () {
    3. return {
    4. musicList: []
    5. }
    6. },
    7. methods: {
    8. // 获取发现歌单
    9. async getGnedan () {
    10. const res = await getMusicList()
    11. console.log(res)
    12. this.musicList = res.data.result
    13. },
    14. // 对播放量进行处理
    15. changeCount: function (num) {
    16. if (num >= 100000000) {
    17. // toFixed(1)显示一位小数
    18. return (num / 100000000).toFixed(1) + '亿'
    19. } else if (num >= 10000) {
    20. return (num / 10000).toFixed(1) + '万'
    21. }
    22. }
    23. },
    24. mounted () {
    25. this.getGnedan()
    26. }
    27. }

    vue3

    1. import { getMusicList } from '@/api/homeApi'
    2. import { reactive, onMounted, toRefs } from 'vue'
    3. export default {
    4. setup () {
    5. const state = reactive({
    6. musicList: []
    7. })
    8. onMounted(async () => {
    9. const res = await getMusicList()
    10. // console.log(res)
    11. state.musicList = res.data.result
    12. })
    13. // 对播放量进行处理
    14. const changeCount = function (num) {
    15. if (num >= 100000000) {
    16. // toFixed(1)显示一位小数
    17. return (num / 100000000).toFixed(1) + '亿'
    18. } else if (num >= 10000) {
    19. return (num / 10000).toFixed(1) + '万'
    20. }
    21. }
    22. return {
    23. ...toRefs(state),
    24. changeCount
    25. }
    26. }
    27. }

    5.组件的模板内使用router-link(路由传参)

    from组件:

    1. <van-swipe-item v-for="item in musicList" :key="item.id">
    2. <router-link :to="{ path: '/itemMusic', query: { id: item.id } }">
    3. <img :src="item.picUrl">
    4. </router-link>
    5. </van-swipe-item>

    to跳转的路由组件进行接收:

    可以通过useRoute()获取到路由信息

    1. import { useRoute } from 'vue-router'
    2. import { onMounted } from 'vue'
    3. // useRoute可以拿到路由的参数
    4. onMounted( () => {
    5. /* 可以调用useRoute方法的query拿到id */
    6. const id = useRoute().query.id
    7. console.log(id)
    8. })

    歌单详情页面

     路由组件:ItemMusic.vue

    组件:

     1.通过父传子值props(Vue3.2语法糖和Vue3写法)

    父组件:

    1. <script setup>
    2. import { useRoute } from 'vue-router'
    3. import { onMounted, reactive } from 'vue'
    4. import { getMusicItem, getMusicItemList } from '@/api/itemApi.js'
    5. /* 引入子组件 */
    6. import ItemMusicTop from '@/components/item/itemMusicTop.vue'
    7. import ItemMusicList from '@/components/item/itemMusicList.vue'
    8. const state = reactive({
    9. playlist: {}, // 歌单详情页数据
    10. itemList: []// 歌单的歌曲
    11. })
    12. // useRoute可以拿到路由的参数
    13. onMounted(async () => {
    14. /* 可以调用useRoute方法的query拿到id */
    15. const id = useRoute().query.id
    16. // console.log(id)
    17. /* 获取歌单详情 */
    18. const res = await getMusicItem(id)
    19. // console.log(res)
    20. state.playlist = res.data.playlist
    21. /* 获取歌单歌曲 */
    22. const result = await getMusicItemList(id)
    23. // console.log(result)
    24. state.itemList = result.data.songs
    25. /* 为防止页面刷新,数据丢失,将数据保存到sessionStorage */
    26. sessionStorage.setItem('itemDetail', JSON.stringify(state))
    27. })
    28. </script>
    29. <template>
    30. <ItemMusicTop :playlist="state.playlist"></ItemMusicTop>
    31. <ItemMusicList :itemList="state.itemList" :subscribedCount="state.playlist.subscribedCount"></ItemMusicList>
    32. </template>
    33. <style lang='less' scoped>
    34. </style>

    子组件:

    vue3.0写法:

    1. <script>
    2. import { reactive } from 'vue'
    3. export default {
    4. props: ['playlist'],
    5. setup (props) {
    6. // 通过props进行传值,判断如果数据拿不到,则从本地读取数据
    7. let creator = reactive({})
    8. if ((props.playlist.creator === '')) {
    9. creator = JSON.parse(sessionStorage.getItem('itemDetail')).playlist.creator
    10. }
    11. // 对播放量进行处理
    12. const changeCount = (num) => {
    13. if (num >= 100000000) {
    14. // toFixed(1)显示一位小数
    15. return (num / 100000000).toFixed(1) + '亿'
    16. } else if (num >= 10000) {
    17. return (num / 10000).toFixed(1) + '万'
    18. }
    19. }
    20. return {
    21. props,
    22. creator,
    23. changeCount
    24. }
    25. }
    26. }
    27. </script>

    vue3.2写法:

    1. <script setup>
    2. import { defineProps } from 'vue'
    3. const props = defineProps(['itemList', 'subscribedCount'])
    4. console.log(props)
    5. </script>

    2.在刷新时读取不到store的数据,则先存储到本地

    保存:

    1. /* 为防止页面刷新,数据丢失,将数据保存到sessionStorage */
    2. sessionStorage.setItem('itemDetail', JSON.stringify(state))

    使用:

    creator = JSON.parse(sessionStorage.getItem('itemDetail')).playlist.creator

    删除:

    sessionStorage.removeItem('itemDetail')

    3.对播放量处理(定义一个函数对渲染的数据进行处理返回出去)

    1. // 对播放量进行处理
    2. const changeCount = (num) => {
    3. if (num >= 100000000) {
    4. // toFixed(1)显示一位小数
    5. return (num / 100000000).toFixed(1) + '亿'
    6. } else if (num >= 10000) {
    7. return (num / 10000).toFixed(1) + '万'
    8. }
    9. }

    在模板使用:

     {{ changeCount(playlist.playCount) }}

    4.Css对图片进行虚化,并将其层级放底层(要定位)

    1. .bgimg {
    2. width: 100%;
    3. height: 11rem;
    4. position: absolute;
    5. z-index: -1;
    6. //虚化
    7. filter: blur(0.6rem);
    8. }

    5.Css文本超出几行就进行省略号表示

    1. span {
    2. width: 80%;
    3. height: .6rem;
    4. text-overflow: ellipsis;
    5. overflow: hidden;
    6. display: -webkit-box; //使用自适应布局
    7. -webkit-line-clamp: 2; //设置超出行数,要设置超出几行显示省略号就把这里改成几
    8. -webkit-box-orient: vertical;
    9. }

    6.用v-for渲染数组时,数组内还有数组的元素可以再v-for进行渲染

    如渲染歌曲列表每一首歌曲,而每一首歌曲又有多少歌手

    1. <div class="item" v-for="(item, index) in itemList" :key="item">
    2. <span class="id">{{ index + 1 }}</span>
    3. <div class="songmsg" @click="playMusic(index)">
    4. <p class="song">{{ item.name }}</p>
    5. <span class="singer" v-for="(item1, i) in item.ar" :key="i">{{ item1.name }}</span>
    6. </div>
    7. </div>

    底部组件FooterMusic.vue

    在App.vue进行引入使用

    需要用到Pinia进行全局数据共享,如底部组件是否显示

    1. <template>
    2. <router-view />
    3. <FooterMusic v-show="store.isFooterMusic"></FooterMusic>
    4. </template>

    1.vue2中的this.$ref在Vue3的使用

    在模板中:

    1. <audio ref="audio" :src="`https://music.163.com/song/media/outer/url?id=${store.playlist[store.
    2. playListIndex].id}.mp3`"></audio>
    1. import { FooterMusicStore } from '@/store/FooterMusic.js'
    2. import { ref, onMounted, watch } from 'vue'
    3. /* vue3中this.$ref的使用变化 */
    4. const audio = ref(null)
    5. onMounted(() => {
    6. console.log(audio)
    7. })

    注意改变audio需要audio.value,因为ref

    2.定时器的使用和清除

    1. // 定时器
    2. let interVal = ref(0)
    3. /* vue3中this.$ref的使用变化 */
    4. const audio = ref(null)
    5. onUpdated(() => {
    6. // 渲染的时候也需要同步歌词时间
    7. updateTime()
    8. })
    9. const play = () => {
    10. /* 判断是否已暂停 */
    11. if (audio.value.paused) {
    12. // 触发定时器
    13. updateTime()
    14. } else {
    15. // 清除定时器
    16. clearInterval(interVal)
    17. }
    18. }
    19. // 设置定时器方法来触发更新歌词时间
    20. const updateTime = () => {
    21. interVal = setInterval(() => {
    22. store.udpateCurrentTime(audio.value.currentTime)
    23. }, 1000)
    24. }

    3.模板字符串的使用

    1. // 获取歌曲歌词/lyric?id=33894312
    2. export function getMusicLyric (data) {
    3. return service({
    4. method: 'GET',
    5. /* 这里用到模板字符串,将data参数传进来 */
    6. url: `/lyric?id=${data}`
    7. })
    8. }

    歌词和磁盘播放页面

     1.旋转图片动画(活跃和不活跃)

    在样式定义好样式和动画

    1. .ar {
    2. width: 3.2rem;
    3. height: 3.2rem;
    4. border-radius: 50%;
    5. position: absolute;
    6. bottom: 3.14rem;
    7. /* 使用动画匀速,无限循环 */
    8. animation: rotate_ar 10s linear infinite;
    9. }
    10. .ar_active {
    11. animation-play-state: running;
    12. }
    13. .ar_paused {
    14. animation-play-state: paused;
    15. }
    16. /* 定义图片旋转动画 */
    17. @keyframes rotate_ar {
    18. 0% {
    19. transform: rotateZ(0deg);
    20. }
    21. 100% {
    22. transform: rotateZ(360deg);
    23. }
    24. }

     通过改变类名进行动画的开始暂停:

     <img :src="musicList.al.picUrl" class="ar" :class="{ ar_active: !isbtnShow, ar_paused: isbtnShow }">
    

    2.歌词

    1. /* 歌词 */
    2. .musiclyricList{
    3. width: 100%;
    4. height: 8rem;
    5. display: flex;
    6. flex-direction: column;
    7. align-items: center;
    8. margin-top: .2rem;
    9. //溢出滚动
    10. overflow: scroll;
    11. p{
    12. color:rgb(195, 239, 244);
    13. margin-bottom: .4rem;
    14. }
    15. //高亮显示的歌词
    16. .active{
    17. color: white;
    18. font-size: .4rem;
    19. }
    20. }

    改变类名实现高亮:

    <p v-for="item in lyric" :key="item" :class="{active:(store.currentTime*1000)>=item.time&&store.currentTime*1000<item.pre}">{{item.lrc}}</p>

    3.计算属性歌词处理(计算属性在vue3.2的使用)

    • 先用数组split方法对歌词的换行进行分割
    •    用map方法,遍历数组并对其进行操作返回一个新数组
    •     以对象形式返回为新数组
    1. import { computed, defineProps, onMounted, ref, watch } from 'vue'
    2. import { Vue3Marquee } from 'vue3-marquee'
    3. import { FooterMusicStore } from '@/store/FooterMusic.js'
    4. import 'vue3-marquee/dist/style.css'
    5. const store = FooterMusicStore()
    6. const props = defineProps(['musicList', 'isbtnShow', 'play', 'addDuration'])
    7. const isLyricShow = ref(false)
    8. // 计算属性歌词处理
    9. const lyric = computed(() => {
    10. let arr
    11. if (store.lyricList.lyric) {
    12. /* 将歌词进行换行符分割 */
    13. /* 1.先用数组split方法对歌词的换行进行分割
    14. 2.用map方法,遍历数组并对其进行操作返回一个新数组
    15. 3.以对象形式返回为新数组
    16. */
    17. arr = store.lyricList.lyric.split(/[(\r\n)\r\n]+/).map((item, i) => {
    18. // 分钟,切割第一到第三
    19. const min = item.slice(1, 3)
    20. // 秒钟切割
    21. const sec = item.slice(4, 6)
    22. // 毫秒切割
    23. let mill = item.slice(7, 10)
    24. // 歌词切割
    25. let lrc = item.slice(11, item.length)
    26. // 每句歌词显示的时间
    27. let time = parseInt(min) * 60 * 1000 + parseInt(sec) * 1000 + parseInt(mill)
    28. // 因为两句歌词后面的毫秒为两位数,则要进行处理
    29. if (isNaN(Number(mill))) {
    30. mill = item.slice(7, 9)
    31. lrc = item.slice(10, item.length)
    32. time = parseInt(min) * 60 * 1000 + parseInt(sec) * 1000 + parseInt(mill)
    33. }
    34. // console.log(min, sec, Number(mill), lrc)
    35. // 返回对象组成数组
    36. return { min, sec, mill, lrc, time }
    37. })
    38. // 遍历拿到pre,即后一句歌词的时间
    39. arr.forEach((item, i) => {
    40. if (i === arr.length - 1 || isNaN(arr[i + 1].time)) {
    41. item.pre = 100000
    42. } else {
    43. item.pre = arr[i + 1].time
    44. }
    45. })
    46. }
    47. return arr
    48. }
    49. )

    4.使用vue3marquee实现歌名走马灯效果

    下载地址:https://www.npmjs.com/package/vue3-marquee

    npm install vue3-marquee@latest --save

    先引入:

    import { Vue3Marquee } from 'vue3-marquee'
    import 'vue3-marquee/dist/style.css'

    1. <!-- 走马灯 -->
    2. <Vue3Marquee>
    3. {{ musicList.name }}
    4. </Vue3Marquee>

    5.watch在vue3.2的使用

    1. // 监听歌词时间
    2. watch(() => store.currentTime, (newValue) => {
    3. const p = document.querySelector('p.active')
    4. // console.log([p])
    5. if (p) {
    6. if (p.offsetTop > 300) {
    7. musicLyric.value.scrollTop = p.offsetTop - 300
    8. }
    9. }
    10. // console.log([musicLyric.value])
    11. if (newValue === store.duration) {
    12. if (store.playListIndex === store.playlist.length - 1) {
    13. store.updateplayListIndex(0)
    14. props.play()
    15. } else {
    16. store.updateplayListIndex(store.playListIndex + 1)
    17. }
    18. }
    19. })

    搜索页面

     1.页面渲染时读取本地历史记录

    1. onMounted(() => {
    2. // 页面渲染时读取本地历史记录
    3. keyWorldList.value = JSON.parse(localStorage.getItem('keyWorldList')) ? JSON.parse(localStorage.getItem('keyWorldList')) : []
    4. })

    2.回车搜索需要进行去重和数组追加(unshift和Set语法)

    1. // 输入框回车操作进行搜索
    2. const enterKey = async () => {
    3. if ((searchKey.value !== '')) {
    4. // 数组向前追加元素
    5. keyWorldList.value.unshift(searchKey.value)
    6. // 去重,这里用到Set语法
    7. keyWorldList.value = [...new Set(keyWorldList.value)]
    8. console.log([...new Set(keyWorldList.value)])
    9. // 固定长度
    10. if (keyWorldList.value.length > 10) {
    11. keyWorldList.value.splice(keyWorldList.value.length - 1)
    12. }
    13. // 将历史记录保存到本地
    14. localStorage.setItem('keyWorldList', JSON.stringify(keyWorldList.value))
    15. const res = await getSearchMusic(searchKey.value)
    16. console.log(res)
    17. // 将请求回来的数据进行接收
    18. searchList.value = res.data.result.songs
    19. searchKey.value = ''
    20. }
    21. }

    登录组件

     1.登录的接口

    1. // 登录/login/cellphone?phone=xxx&password=yyy
    2. export function getPhoneLogin (data) {
    3. return service({
    4. method: 'GET',
    5. url: `/login/cellphone?phone=${data.phone}&password=${data.password}`
    6. })
    7. }

    2.登录需要的数据共享

    1. import { getPhoneLogin } from '@/api/homeApi.js'
    2. import { defineStore } from 'pinia'
    3. export const FooterMusicStore = defineStore('musicstore', {
    4. state: () => {
    5. return {
    6. isLogin: false, // 登录状态
    7. isFooterMusic: true, // 判断底部组件是否显示
    8. token: '', // 接收后台返回的token字段
    9. user: {}// 用户信息
    10. }
    11. },
    12. getters: {
    13. },
    14. actions: {
    15. // 登录请求
    16. async getLogin (value) {
    17. const res = await getPhoneLogin(value)
    18. console.log('登录返回的数据:', res)
    19. return res
    20. },
    21. // 更新登录状态
    22. udpateIsLogin (value) {
    23. this.isLogin = value
    24. },
    25. // 更新token字段
    26. updateToken (value) {
    27. this.token = value
    28. localStorage.setItem('token', this.token)
    29. },
    30. // 更新用户信息
    31. updateUser (value) {
    32. this.user = value
    33. localStorage.setItem('mydata', JSON.stringify(this.user))
    34. }
    35. }
    36. })

    3.登录路由组件

    1. <!-- 登录路由组件 -->
    2. <script setup>
    3. /* 在setup中使用访问路由 */
    4. import { useRouter } from 'vue-router'
    5. import { ref } from 'vue'
    6. import { FooterMusicStore } from '@/store/FooterMusic.js'
    7. import { getLoginUser } from '@/api/homeApi.js'
    8. const store = FooterMusicStore()
    9. const router = useRouter()
    10. const phone = ref('')
    11. const password = ref('')
    12. const Login = async () => {
    13. const res = await store.getLogin({ phone: phone.value, password: password.value })
    14. console.log(res)
    15. if (res.data.code === 200) {
    16. // 将用户登录状态传过去
    17. store.udpateIsLogin(true)
    18. // 将用户id传到发起获取用户详情的接口
    19. const result = await getLoginUser(res.data.account.id)
    20. console.log('获取用户详情返回的数据:', result)
    21. // 将后端返回来的token传去pinia和本地存储
    22. store.updateToken(res.data.token)
    23. // 将用户详情数据存储到pinia和本地存储
    24. store.updateUser(result)
    25. // 如果返回的code为200,说明登录成功,跳转个人中心页面
    26. router.push('/infoUser')
    27. } else {
    28. alert('手机号码或密码错误!')
    29. }
    30. }
    31. </script>
    32. <template>
    33. <div class="loginBox">
    34. <div class="title">
    35. 欢迎登录
    36. </div>
    37. <div class="form" @keydown.enter="Login">
    38. <input type="text" placeholder="请输入手机号" v-model="phone">
    39. <input type="password" placeholder="请输入密码" v-model="password">
    40. <van-button color="linear-gradient(to right, #ff6034, #ee0a24)" @click="Login">
    41. 登录
    42. </van-button>
    43. </div>
    44. <van-button color="linear-gradient(to right, #ff6034, #ee0a24)" @click="$router.go(-1)">
    45. 返回首页
    46. </van-button>
    47. </div>
    48. </template>

    源码icon-default.png?t=M4ADhttps://gitee.com/zi1726517395/emo_music.git

  • 相关阅读:
    数据结构初步(五)- 线性表之单链表的分析与C语言实现
    微信小程序——后台交互
    安装或卸载Anaconda后Windows自带的cmd命令行窗口会闪退
    Git:撤销 commit 提交或撤销对远程仓库的push操作
    MySQL基础—从零开始学习MySQL
    【python学习】基础篇-常用模块-re模块:正则表达式高效操作字符串
    vivo面试-Java
    Spring框架在Bean中的管理(第十课)
    Node.js内置模块
    Elasticsearch搜索引擎该怎么使用,这篇文章彻底讲透(荣耀典藏版)
  • 原文地址:https://blog.csdn.net/m0_61612505/article/details/124944861