Day42:网易云项目,路由进阶

网易云项目

创建、启动项目并配置路由

npm init vite

npm i

npm i vue-router

npm i sass -D

在main.js中

import router from './router'
createApp(App).use(router).mount('#app')

在index中配置路由

import {createRouter,createWebHistory} from 'vue-router'
import Home from '../view/Home.vue'
import Search from '../view/Search.vue'
const router = createRouter ({
    history:createWebHistory(),
    routes:[
        {
            path:'/',
            name:'home',
            component:Home
        },
        {
            path:'/search',
            name:'search',
            component:Search
        }
    ]
})
export default router

实现全局通用导航栏

写一个顶部的导航栏TopNav.vue在component文件夹中

<template>
  <div class="top-nav">
    <div class="w content">
      <router-link to="/"><h1 class="logo"></h1></router-link>
      <!-- 绑定一个导航,点击回到首页 -->
      <div class="search-input">
        <input type="text" 
          placeholder="请输入"
          v-model.trim="song"
          @keyup.enter="searchSong"
          />
      </div>
    </div>
  </div>
</template>
<script setup>
  import { ref } from "vue";
  import { useRouter } from "vue-router";
  const song = ref("")
  const router = useRouter()
  function searchSong(){
    router.push(`/search?song=${song.value}`)
  }
</script>
<style lang="scss" scoped>
  .top-nav {
    height: 70px;
    background-color: #242424;
    .content {
      display: flex;
      align-items: center;
      justify-content: space-between;
      h1 {
        width: 176px;
        height: 70px;
        background-image: url("https://s2.music.126.net/style/web2/img/frame/topbar.png?19dafe7dd55400ffec9b354833340e1f");
        background-position: 0 0;
      }
      .search-input {
        input {
          height: 40px;
          border-radius: 18px;
          padding: 5px 10px;
          outline: none;
        }
      }
    }
  }
</style>

useRoute和useRouter的区别,一个是获取全局路由,一个是获取当前页面的路由

在app中,将样式引入,并且去掉<style lang="scss" scoped>中的scoped,使导航栏能在全局应用

<template>
  <div>
      <top-nav/> <!-- 普通组件,不参与路由切换,是全局样式 -->
      <router-view></router-view>
  </div>
</template>

<script setup>
import TopNav from './components/TopNav.vue';
</script>

<style lang="scss">// scoped去掉,可以作为全局样式
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
.w {
  width: 980px;
  margin: 0 auto;
}
</style>

获取axios请求的内容

在获取网易云数据的api中note app.js

在项目中

npm i axios

获取搜索的接口,放在search中,把连接板包裹在axios把获取的song.value拼接到链接后面,然后发axios请求

  const route = useRoute()
  const song = ref("")
  const lists = ref([])
  song.value = route.query.song
  axios.get(`http://localhost:3000/search?keywords=${song.value}`).then((res) => {
    lists.value = res.data.result.songs
  })

查询到数据得到一个30长度的array

将array中需要的数据渲染到页面

<template>
  <div class="search">
    <div class="w list">
      <ul>
        <li 
          v-for="(item, index) in lists" 
          :key="item.id" 
          :class="{ active: index % 2 === 1 }">
          <p>{{ item.name }}</p>
          <span>
            <span 
              class="inner_span" 
              v-for="_item, _index in item.artists" 
              :key="_index"
              >{{ _item.name }}
              {{ _index + 1 < item.artists.length ? "/" : "" }}</span></span>
          <span>{{ item.album.name }}</span>
          <span>{{ format(item.duration) }}</span><!-- 格式化毫秒 -->
        </li>
      </ul>
    </div>
  </div>
</template>

得到的歌手不换行,超出的部分省略号,给innerspan添加三个样式即可

overflow: hidden;

white-space: nowrap;

text-overflow: ellipsis;

给每个歌手添加后斜杠,如果索引值+1小于数组长度就加斜杠,如果索引值+1不小于数组长度就不加斜杠{{_index + 1 < item.artists.length ? "/" : ""}}

给li添加斑马条纹样式和鼠标hover样式

<style lang="scss" scoped>
  .search {
    background-color: #eee;
    .list {
      background-color: #fff;
      border-left: 1px solid #615f5f;
      border-right: 1px solid #615f5f;
      ul {
        list-style: none;
        padding: 20px;
        li {
          display: flex;
          align-items: center;
          padding: 12px;
          gap: 10px;
          &.active {
            background-color: #f9f5f5;
          }
          &:hover {
            background-color: #e0dbdb;
            cursor: pointer;
          }
          p {
            width: 40%;
          }
          & > span {
            flex: 1;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
        }
      }
    }
  }
</style>

定义方法format用来毫秒值转分秒

分钟和秒如果不足10需要在前面添加字符串0。如果秒只有一位,获取秒数时只截取一位,然后添加字符串0;如果多于10,则截取前两位,后面多余的不要。

<script setup>
  import { useRoute } from "vue-router";
  import { ref } from "vue";
  import { format } from "path";
  import axios from "axios";
  function format(duration) {
    //毫秒转分秒
    let mins = Math.floor(duration / 1000 / 60)
    // 如果只有个位数要补一个0在前面
    if (mins < 10) {
      mins = "0" + mins
    }
    // 获取秒
    let secs = (duration / 1000) % 60
    // 字符串截取
    if (secs > 10) {
      secs = String(secs).slice(0, 2)
    } else {
      secs = "0" + String(secs)[0] // 只有一位前面加零,后面只截取一位
    }
    return mins + ':' + secs
  }
  const route = useRoute()
  const song = ref("")
  const lists = ref([])
  song.value = route.query.song
  axios.get(`http://localhost:3000/search?keywords=${song.value}`).then((res) => {
    lists.value = res.data.result.songs
  })
</script>

在search页面重新搜索

需要监听song,如果song变化,需要重新发送请求,再把参数传给lists

封装axios请求为方法init(song)

在第一次加载的时候先运行一次init(song.value)

需要监听route.query.song的变化,变化了重新执行init

route.query是一个响应式对象,但route.query.song是一个响应式对象里的值,如果要对这个值进行监听,需要写一个函数包装他,并返回值,这样就不会出现报错了

封装了之后,axios请求中包裹的动态字符中的.value需要删除,因为监听已经直接针对song

const route = useRoute()
const song = ref("")
const lists = ref([]) 
song.value = route.query.song
function init(song) {
    axios.get(`http://localhost:3000/search?keywords=${song}`).then((res) => {
        lists.value = res.data.result.songs
    })
}
init(song.value) // 第一次加载的时候执行
watch(
    () => route.query.song,
    (newValue) => {
        song.value = newValue
        init(song.value)
    })

登录页面项目

  1. 在login页面设置登录按钮,如果点击则传isLogin为true到本地存储,表示已经登录
<template>
    <div>
        this is login
        <button @click="login">login</button>
    </div>
</template>
<script setup>
function login() {
    //登录身份信息(token信息)只要登陆了,就在本地存,且设置为true
    localStorage.setItem('isLogin',true)
  // 在本地存储中找,如果有该字段且为true,则认为登录
}
</script>
  1. 在进入任何页面(除开login之外),要做一个身份认证,如果已经登录,可以进入这个页面,否则进入login身份认证
<template>
  <div>
      <router-view></router-view>
  </div>
</template>
  1. 在生命周期钩子函数router.beforeEach进入到每个页面之前,都要进行钩子函数触发,被称为全局的前置路由导航守卫。
  2. 如果进入的页面不是login,或者没有登录(isLogin不是true),则强制回到login
    1. router.beforeEach((to,from) => {})参数to代表去哪个页面,from表示从哪里来
    2. 此代码写在路由页面router
router.beforeEach((to,from) => {
    // 如果进入了页面不是login,或者isLogin不是true,则强制回到login
    if(to.name !== 'login' && !localStorage.getItem("isLogin")){
        return {
            name: "login"
        }
    }
})
单个路由导航守卫

router是全局路由,如果想精细化定制不同页面的跳转效果,可以在路由导航列表的对象里面书写路由导航守卫

localStorage.getItem("isLogin")需要用JSON包裹,否则可能会出现不能识别的情况

{
    path: '/detail',
    name: 'detail',
    component: Detail,
    // 单个路由对象的前置导航守卫
    beforeEnter (to, from) {
        if (to.name !== "/login" && 
        !JSON.parse(localStorage.getItem("isLogin"))
        ) {
            return {
                name: "login"
            }
        }
    }
},

前置导航守卫可以设置单个,但后置导航守卫可以设置全局的

前置导航守卫可以写在router的全局、局部,或者写在组件内部。推荐写在router

组件内部写法

setup语法糖无法触发beforeRouteEnter钩子函数,需要写完整格式

<script>
/* import { onBeforeEnter } from "vue-router";
onBeforeEnter() */
export default {
    beforeRouteEnter (to, from) {
        console.log('beforeenter')
    }
}
</script>

离开当前路由时触发导航守卫

<script>
    // 离开当前路由时,触发一个导航守卫
    beforeRouteLeave (to, from) {
        let res = window.confirm('您是否想离开本页面')
        if(!res) {
            return false // 如果是false,则留在本页面
        }
    }
}
</script>

路由进阶

1.路由导航守卫

顾名思义,路由导航守卫可以让每个页面完成跳转前后进行一些逻辑实现。vue-router提供了多种导航守卫:全局的,单个路由独享的,或者组件级的。现在来逐一说明。

路由导航守卫的详细说明

1.1 全局前置守卫

你可以使用 router.beforeEach 注册一个全局前置守卫,这样在匹配每个路由路径的时候都会进行一次进入前的"拦截",在某些需要验证登录状态的业务场景下应用极广。

我们仍然用示例来演示全局前置守卫的使用:

现在有3个页面,分别是登录页,首页,列表页。进入任何页面前都需要进行身份验证,必须本地存储中isLogin的字段为true时才可以进入,否则统一回到登录页。按照之前学习的内容快速完成路由信息的相关配置。

在配置完路由信息后,通过router.beforeEach 注册一个全局前置守卫,其中的守卫方法接受2个参数

  • to:表示当前导航即将进入的页面
  • from:表示当前导航即将离开的页面

前置守卫中通过返回的路由对象实现重定向。

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../components/Home.vue'
import Detail from '../components/Detail.vue'
import Login from '../components/Login.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'home',
      components: Home,
    },
    {
      path: '/detail',
      name: 'detail',
      components: Detail,
    },
    {
      path: '/login',
      name: 'login',
      components: Login,
    },
  ],
})
router.beforeEach((to, from) => {
  if (!localStorage.getItem('isLogin') && to.name !== 'login') {
    return {
      name: 'login',
    }
  }
})
export default router

在之前版本的路由中,还提供了第三个可选参数next,通过调用next方法也能进入到即将进入的路由页面,但新版本中不推荐这么做了。

1.2 全局后置守卫

和全局前置守卫类似,同样可以注册全局后置守卫,沿用上面的示例,在离开每个页面之前,都弹出一个提示框。

router.afterEach((to, from) => {
  alert('要离开此页面咯')
})
1.3 独享前置守卫

除了定义全局的路由守卫,还可以针对单个路由对象定义守卫:

{
  path: '/detail',
    name: 'detail',
    component: Detail,
    beforeEnter: (to, from) => {
    if (!localStorage.getItem('isLogin') && to.name !== 'login') {
      return {
        name: 'login',
      }
    }
  },
},
1.4 组件内部的路由守卫

除开在路由配置信息中定义导航守卫,还可以在组件内部定义守卫:

  • beforeRouteEnter 进入路由前调用
  • beforeRouteUpdate 动态参数改变时调用
  • beforeRouteLeave 离开路由前调用

2.路由元信息

meta叫路由元信息,可以存储当前路由对象的一些信息

可以将任意信息添加到路由对象上,比如页面标题,鉴权认证等,Vue中的路由提供了一个meta字段,可以通过给每个路由对象配置meta字段来添加信息。

{
  path: '/',
    name: 'home',
    component: Home,
    meta: {
    title: '首页',
      },
},

在组件的生命周期中,可以将meta信息合理地调用出来。

//Home中 组件挂载完毕后 将页面的标题内容改为meta中存放的信息
<script setup>
  import { onMounted } from 'vue'
  import { useRoute } from 'vue-router'
  onMounted(() => {
    document.title = useRoute().meta.title
  })
</script>

这里需要写setup语法糖,但是组件内守卫不要写。虽然最好也不要写组件内守卫啦

<script setup>
  import { onMounted } from 'vue'
  import { useRoute } from 'vue-router'
  const route = useRoute() // 最好写在外面,不要写在钩子里
  onMounted(() => {
    document.title = route.meta.title
  })
</script>

3.路由嵌套

某些复杂的组件关系存在层级更深的路由,这是可以利用路由嵌套来实现,给需要添加二级路由的路由对象添加children属性来配置二级路由,同样使用来渲染出口。

现在给Detail路由页面添加二级子路由,能够显示用户详情和头像详情,在对应的路由配置中添加children属性

// 不要忘记最上面的引入!!!
{
  path: '/detail',
    name: 'detail',
    component: Detail,
    meta: {
    title: '详情页',
      },
  children: [
    { path: 'users', name: 'users', component: Users },
    { path: 'profile', name: 'profile', component: Profile },
  ],
    },

建立好对应的路由组件后,需要在Detail中添加二级路由的出口:

<template>
  <h1>这是详情页</h1>
  <router-link to="users"> <button>用户详情页</button></router-link>
  <router-link to="profile"> <button>头像详情页</button></router-link>
  <router-view></router-view> // 记得渲染出口
</template>

嵌套路由的使用规则跟一级路由保持一致,当然如果你的业务足够复杂,还可以嵌套三级,四级路由。

4.路由滚动行为

切换路由的过程中,VueRouter还提供了操作滚动条行为的方法,通过scrollBehavior方法可以在路由配置中规定滚动条行为

const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
    return {
      top:30 //滚动到距离顶部30
    }
  }
})

5.路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

首页不用做懒加载,除开首页对所有的路由都使用动态导入。

Vue Router 支持开箱即用的动态导入,这意味着你可以用动态导入代替静态导入:

const Detail = () => import('../components/Detail.vue')

const router = createRouter({
  // ...
  routes: [{ path: '/detail',name:'detail', component: Detail }],
})
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../view/Home.vue'
const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/login',
            name: 'login',
            component: ()=>import('../view/Login.vue'), // 改为懒加载
            meta: {
                title: '登录',
            },
        },

6.动态路由 (路由权限)

在登录页点击登录后,button新增了一个vip的button,可以进入vip页面。登录之后,才能在路由映射关系中加上vip对象(不能是控制按钮显示隐藏

对路由的添加通常是通过 配置routes来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由。例如在处理一些路由权限的场景下就需要动态增加路由。

6.1 增加路由

动态增加路由主要依靠router.addRoute()方法实现,获取到router实例后,可以在合理时机注册新的路由对象,

例如在登陆后新增一个Vip路由:

const login = () => {
  localStorage.setItem('isLogin', true)
  isVip.value = true
  router.addRoute({
    path: '/vip',
    name: 'vip',
    component: Vip,
  })
}
6.2 删除路由

动态删除路由最常用的方法是通过使用 router.removeRoute() 按名称删除路由,所以注册路由信息时添加name属性是一个好习惯,大家务必养成。

我们示例中登录退出希望将Vip路由删除掉:

const out = () => {
  localStorage.clear()
  isVip.value = false
  router.removeRoute('vip')
}

最终Login组件的页面代码会显得比较丰富完整:

<template>
  <router-link to="/"><button>首页</button></router-link>
  <router-link to="/detail"><button>详情页</button></router-link>
  <router-link to="/vip" v-if="isVip"><button>Vip页</button></router-link>
  <button @click="login">登录</button>
  <button @click="out">退出登录</button>
</template>

<script setup>
  import { ref } from 'vue'
  import { useRouter } from 'vue-router'
  import Vip from '../components/Vip.vue'
  const router = useRouter()
  const login = () => {
    localStorage.setItem('isLogin', true)
    isVip.value = true
    router.addRoute({ // 登录成功后再添加路由映射关系
      path: '/vip',
      name: 'vip',
      component: Vip,
    })
  }
  const out = () => {
    localStorage.clear()
    // localStorage.removeItem('isLogin') //去掉登录身份信息
    isVip.value = false
    router.removeRoute('vip') // 退出登录后动态删除路由
  }
  let isVip = ref(localStorage.getItem('isLogin'))
</script>

<style></style>
6.3 查看当前路由

Vue Router 提供了两个功能来查看现有的路由:

  • router.hasRoute():检查路由是否存在。
  • router.getRoutes():获取一个包含所有路由记录的数组。
<template>
    <div>
        this is login
        <button @click="login">login</button>
        <button @click="logout">logout</button>
        <button @click="$router.push('/')">首页</button>
        <button @click="$router.push('/detail')">详情</button>
        <router-link to="/vip" v-if="isVip"><button>Vip页</button></router-link>
    </div>
</template>
<script setup>
import {onMounted,ref} from 'vue'
import { useRoute } from 'vue-router'
import { useRouter } from 'vue-router'
import Vip from './Vip.vue'
const router = useRouter()
// 使用json包裹,使isVip的状态依靠isLogin的状态改变
const isVip = ref(JSON.parse(localStorage.getItem("isLogin")))
function login() {
    //登录身份信息(token信息)只要登陆了,就在本地存,且设置为true
    localStorage.setItem('isLogin',true) // 在本地存储中找,如果有该字段且为true,则认为登录
    isVip.value = true
    // 登录之后,才在路由映射关系中加上vip对象
    router.addRoute({
        path: '/vip',
        name: 'vip',
        component: Vip,
    })
}
function logout() {
    //去掉登录身份信息
    localStorage.removeItem('isLogin')
    isVip.value = false
    // 动态删除路由
    router.removeRoute("vip")
}
const route = useRoute() // 最好写在外面,不要写在钩子里
onMounted(() => {
    document.title = route.meta.title
})
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值