vue-router 详细讲解 !!!

前端路由

前端路由是后来发展到SPA(单页应用)时才出现的概念。 SPA 就是一个WEB项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。

前端路由在SPA项目中是必不可少的,页面的跳转、刷新都与路由有关,通过不同的url显示相应的页面。

优点:前后端的彻底分离,不刷新页面,用户体验较好,页面持久性较好。

后端路由

当在地址栏切换不同的url时,都会向服务器发送一个请求,服务器接收并响应这个请求,在服务端拼接好html文件返回给页面来展示。

优点:减轻了前端的压力,html都由后端拼接;

缺点:依赖于网络,网速慢,用户体验很差,项目比较庞大时,服务器端压力较大,

不能在地址栏输入指定的url访问相应的模块,前后端不分离。

路由模式

前端路由实现起来其实很简单,本质是监听 URL 的变化,然后匹配路由规则,在不刷新的情况下显示相应的页面。

hash模式(对应HashHistory)
  • 把前端路由的路径用井号 # 拼接在真实 url 后面的模式,但是会覆盖锚点定位元素的功能,通过监听 URL 的哈希部分变化,相应地更新页面的内容。
  • 前端路由的处理完全在客户端进行,在路由发生变化时,只会改变 URL 中的哈希部分(井号 # 后面的路径),且不会向服务器发送新的请求,而是触发 onhashchange 事件。
  • hash 只有#符号之前的内容才会包含在请求中被发送到后端,如果 nginx 没有匹配得到当前的 url 也没关系。hash 永远不会提交到 server 端。
  • hash值的改变,都会在浏览器的访问历史中增加一个记录,所以可以通过浏览器的回退、前进按钮控制hash的切换。
  • hash 路由不会造成 404 页面的问题,因为所有路由信息都在客户端进行解析和处理,服务器只负责提供应用的初始 HTML 页面和静态资源,不需要关心路由的匹配问题。
// onhashchage事件,可以在window对象上监听这个事件
window.onhashchange = function(event){
  console.log(event.oldURL, event.newURL)
  let hash = location.hash.slice(1)
}
  • 通过location.hash修改hash值,触发更新。
  • 通过监听hashchange事件监听浏览器前进或者后退,触发更新。
history模式 (对应HTML5History)
  • 是 html5 新推出的功能,比 Hash url 更美观
  • 在 history 模式下浏览器在刷新页面时,会按照路径发送真实的资源请求。如果 nginx 没有匹配得到当前的 url ,就会出现 404 的页面。
  • 在使用 history 模式时,需要通过服务端支持允许地址可访问,如果没有设置,就很容易导致出现 404 的局面。
  • 改变url: history 提供了 pushState 和 replaceState 两个方法来记录路由状态,这两个方法只改变 URL 不会引起页面刷新。
  • 监听url变化:通过 onpopstate 事件监听history变化,在点击浏览器的前进或者后退功能时触发,在onpopstate 事件中根据状态信息加载对应的页面内容。
history.replaceState({}, null, '/b') // 替换路由
history.pushState({}, null, '/a') // 路由压栈,记录浏览器的历史栈 不刷新页面
history.back() // 返回
history.forward() // 前进
history.go(-2) // 后退2次

history.pushState 修改浏览器地址,而页面的加载是通过 onpopstate 事件监听实现,加载对应的页面内容,完成页面更新。

// 页面加载完毕 first.html
history.pushState({page: 1}, "", "first.html");

window.onpopstate = function(event) {
  // 根据当前 URL 加载对应页面
  loadPage(location.pathname); 
};

// 点击跳转到 second.html
history.pushState({page: 2}, "", "second.html");

function loadPage(url) {
  // 加载 url 对应页面内容
  // 渲染页面
}

onpopstate 事件是浏览器历史导航的核心事件,它标识了页面状态的变化时机。通过监听这个时机,根据最新的状态信息更新页面

当使用 history.pushState() 或 history.replaceState() 方法修改浏览器的历史记录时,不会直接触发 onpopstate 事件。

但是,可以在调用这些方法时将数据存储在历史记录条目的状态对象中, onpopstate 事件在处理程序中访问该状态对象。这样,就可以在不触发 onpopstate 事件的情况下更新页面内容,并获取到相应的状态值。

history 模式下 404 页面的处理

在 history 模式下,浏览器会向服务器发起请求,服务器根据请求的路径进行匹配:

如果服务器无法找到与请求路径匹配的资源或路由处理器,服务器可以返回 /404 路由,跳转到项目中配置的 404 页面,指示该路径未找到。

对于使用历史路由模式的单页应用(SPA),通常会在服务器配置中添加一个通配符路由,将所有非静态资源的请求都重定向到主页或一个自定义的 404 页面,以保证在前端处理路由时不会出现真正的 404 错误页面。

在项目中配置对应的 404 页面:

export const publicRoutes = [
  {
    path: '/404',
    component: () => import('src/views/404/index'),
  },
]

vueRouter

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,允许你在 Vue 应用中构建单页面应用(SPA),并且提供了灵活的路由配置和导航功能。让用 Vue.js 构建单页应用变得轻而易举。功能包括:

  • 路由映射:可以将 url 映射到 Vue组件,实现不同 url 对应不同的页面内容。
  • 嵌套路由映射:可以在路由下定义子路由,实现更复杂的页面结构和嵌套组件的渲染。
  • 动态路由:通过路由参数传递数据。你可以在路由配置中定义带有参数的路由路径,并通过 $route.params 获取传递的参数。
  • 模块化、基于组件的路由配置:路由配置是基于组件的,每个路由都可以指定一个 Vue 组件作为其页面内容,将路由配置拆分为多个模块,在需要的地方引入。。
  • 路由参数、查询、通配符:通过路由参数传递数据,实现页面间的数据传递和动态展示。
  • 导航守卫:Vue Router 提供了全局的导航守卫和路由级别的导航守卫,可以在路由跳转前后执行一些操作,如验证用户权限、加载数据等。
  • 展示由 Vue.js 的过渡系统提供的过渡效果:可以为路由组件添加过渡效果,使页面切换更加平滑和有动感。
  • 细致的导航控制:可以通过编程式导航(通过 JavaScript 控制路由跳转)和声明式导航(通过 组件实现跳转)实现页面的跳转。
  • 路由模式设置:Vue Router 支持两种路由模式:HTML5 history 模式或 hash 模式
  • 可定制的滚动行为:当页面切换时,Vue Router 可以自动处理滚动位置。定制滚动行为,例如滚动到页面顶部或指定的元素位置。
  • URL 的正确编码:Vue Router 会自动对 URL 进行正确的编码

路由组件

  • **router-link:**通过 router-link 创建链接 其本质是a标签,这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
  • **router-view:**router-view 将显示与 url 对应的组件。

$router$route

$route: 是当前路由信息对象,获取和当前路由有关的信息。 route 为属性是只读的,里面的属性是 immutable (不可变) 的,不过可以通过 watch 监听路由的变化。

fullPath: ""  // 当前路由完整路径,包含查询参数和 hash 的完整路径
hash: "" // 当前路由的 hash 值 (锚点)
matched: [] // 包含当前路由的所有嵌套路径片段的路由记录 
meta: {} // 路由文件中自赋值的meta信息
name: "" // 路由名称
params: {}  // 一个 key/value 对象,包含了动态片段和全匹配片段就是一个空对象。
path: ""  // 字符串,对应当前路由的路径
query: {}  // 一个 key/value 对象,表示 URL 查询参数。跟随在路径后用'?'带的参数

$router 是 vueRouter 实例对象,是一个全局路由对象,通过 this.$router 访问路由器, 可以获取整个路由文件或使用路由提供的方法。

// 导航守卫
router.beforeEach((to, from, next) => {
  /* 必须调用 `next` */
})
router.beforeResolve((to, from, next) => {
  /* 必须调用 `next` */
})
router.afterEach((to, from) => {})

动态导航到新路由
router.push
router.replace
router.go
router.back
router.forward

routes 是 router 路由实例用来配置路由对象 可以使用路由懒加载(动态加载路由)的方式

  • 把不同路由对应的组件分割成不同的代码块,当路由被访问时才去加载对应的组件 即为路由的懒加载,可以加快项目的加载速度,提高效率
const router = new VueRouter({
  routes: [
    {
      path: '/home',
      name: 'Home',
      component:() = import('../views/home')
		}
  ]
})

vueRouter的使用

页面中路由展示位置

​
<div id="app">
  <!-- 添加路由 -->
  <!-- 会被渲染为 <a href="#/home"></a> -->
  <router-link to="/home">Home</router-link>
  <router-link to="/login">Login</router-link>
  <!-- 展示路由的内容 -->
  <router-view></router-view>
</div>


​

路由模块 引入 vue-router,使用 Vue.use(VueRouter) 注册路由插件 定义路由数组,并将数组传入VueRouter 实例,并将实例暴露出去

import Vue from 'vue'
import VueRouter from 'vue-router'
import { hasVisitPermission, isWhiteList } from './permission'

// 注册路由组件
Vue.use(VueRouter)

// 创建路由: 每一个路由规则都是一个对象
const routers =[
  // path 路由的地址
  // component 路由的所展示的组件
  {
      path: '/',
      // 当访问 '/'的时候 路由重定向 到新的地址 '/home'
      redirect: '/home',
  },     
  {
      path: '/home',
      component: home,
  },
  {
      path: '/login',
      component: login,
  },
],

// 实例化 VueRouter 路由
const router = new VueRouter({
  mode: 'history',
  base: '/',
  routers
})

// 路由守卫
router.beforeEach(async (to, from, next) => {
  // 清除面包屑导航数据
  store.commit('common/SET_BREAD_NAV', [])
  // 是否白名单
  if (isWhiteList(to)) {
    next()
  } else {
    // 未登录,先登录
    try {
      if (!store.state.user.userInfo) {
        await store.dispatch('user/getUserInfo')
      }

      // 登录后判断,是否有访问页面的权限
      if (!hasVisitPermission(to, store.state.user.userInfo)) {
        next({ path: '/404' })
      } else {
        next()
      }
    } catch (err) {
      $error(err)
    }
  }
})

export default router

在 main.js 上挂载路由 将VueRouter实例引入到main.js,并注册到根Vue实例上

import router from './router'

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

动态路由

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。我们可以在 vueRrouter 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果。

  • 动态路由的创建,主要是使用 path 属性过程中,使用动态路径参数,路径参数 用冒号 : 表示。

当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.query 的形式暴露出来。因此,我们可以通过更新 User 的模板来呈现当前的用户 ID:

_vue-router _通过配置 _params _和 _query _来实现动态路由

params 传参
  • 必须使用 命名路由 name 传值

  • 参数不会显示在 url 上

  • 浏览器强制刷新时传参会被清空

// 传递参数
this.$router.push({
  name: Home,
  params: {
    number: 1 ,
    code: '999'
  }
})
// 接收参数
const p = this.$route.params
query 传参
  • 可以用 name 也可以使用 path 传参
  • 传递的参数会显示在 url 上
  • 页面刷新是传参不会丢失
// 方式一:路由拼接
this.$router.push('/home?username=xixi&age=18')

// 方式二:name + query 传参
this.$router.push({
  name: Home,
  query: {
    username: 'xixi',
    age: 18
	}
})


// 方式三:path + name 传参
this.$router.push({
  path: '/home',
  query: {
    username: 'xixi',
    age: 18
	}
})

// 接收参数
const q = this.$route.query

keep-alive

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

keep-alive 可以设置以下props属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

在不缓存组件实例的情况下,每次切换都会重新 render,执行整个生命周期,每次切换时,重新 render,重新请求,必然不满足需求。 会消耗大量的性能

keep-alive 的基本使用

只是在进入当前路由的第一次render,来回切换不会重新执行生命周期,且能缓存router-view的数据。 通过 include 来判断是否匹配缓存的组件名称: 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配

<keep-alive>
	<router-view></router-view>
</keep-alive>

<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

路由配置 keepAlive

在路由中设置 keepAlive 属性判断是否需要缓存

{
  path: 'list',
  name: 'itemList', // 列表页
  component (resolve) {
    require(['@/pages/item/list'], resolve)
 	},
   meta: {
    keepAlive: true,
    compName: 'ItemList'
    title: '列表页'
   }
}

{
  path: 'management/class_detail/:id/:activeIndex/:status',
  name: 'class_detail',
  meta: {
    title: '开班详情',
    keepAlive: true,
    compName: 'ClassInfoDetail',
    hideInMenu: true,
  },
  component: () => import('src/views/classManage/class_detail.vue'),
},

使用

<div id="app" class='wrapper'>
  <keep-alive>
      <!-- 需要缓存的视图组件 --> 
      <router-view v-if="$route.meta.keepAlive"></router-view>
   </keep-alive>
    <!-- 不需要缓存的视图组件 -->
   <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

keepAlive 对生命周期的影响

设置缓存后组件加载的生命周期会新增 actived 与 deactived

  • 首次进入组件时也会触发 actived 钩子函数:beforeRouteEnter > beforeCreate > created> beforeMount > beforeRouteEnter 的 next 回调> mounted > activated > ... ... > beforeRouteLeave > deactivated
  • 再次进入组件时直接获取actived的组件内容:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

keep-alive 组件监听 include 及 exclude 的缓存规则,若发生变化则执行 pruneCache (遍历cache 的name判断是否需要缓存,否则将其剔除) 且 keep-alive 中没有 template,而是用了 render,在组件渲染的时候会自动执行 render 函数,

  • 若命中缓存则直接从缓存中拿 vnode 的组件实例,
  • 若未命中缓存且未被缓存过则将该组件存入缓存,
  • 当缓存数量超出最大缓存数量时,删除缓存中的第一个组件。

动态路由缓存的的具体表现在:

  • 由动态路由配置的路由只能缓存一份数据。
  • keep-alive 动态路由只有第一个会有完整的生命周期,之后的路由只会触发 actived 和 deactivated这两个钩子。
  • 一旦更改动态路由的某个路由数据,期所有同路由下的动态路由数据都会同步更新。

如何删除 keep-alive 中的缓存

vue2 中清除路由缓存

在组件内可以通过 this 获取 vuerouter 的缓存
vm.$vnode.parent.componentInstance.cache

或者通过 ref 获取 外级 dom

<template>
  <el-container id="app-wrapper">
    <Aside />
    <el-container>
      <el-header id="app-header" height="45px">
        <Header @removeCacheRoute="removeCacheRoute" />
      </el-header>
      <!-- {{ includeViews }} -->
      <el-main id="app-main">
        <keep-alive :include="includeViews">
          <router-view ref="routerViewRef" :key="key" />
        </keep-alive>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
import Aside from './components/Aside'
import Header from './components/Header'
import { mapGetters } from 'vuex'
export default {
  name: 'Layout',
  components: {
    Aside,
    Header,
  },
  data () {
    return {
    }
  },
  computed: {
    ...mapGetters(['cacheRoute', 'excludeRoute']),
    includeViews () {
      return this.cacheRoute.map(item => item.compName)
    },
    key () {
      return this.$route.fullPath
    },
  },
  methods: {
    removeCacheRoute (fullPath) {
      const cache = this.$refs.routerViewRef.$vnode.parent.componentInstance.cache
      delete cache[fullPath]
    },
  },
}
</script>

路由守卫

导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

通俗来讲:路由守卫就是路由跳转过程中的一些生命周期函数(钩子函数),我们可以利用这些钩子函数帮我们实现一些需求。

路由守卫又具体分为 全局路由守卫独享守卫组件路由守卫

全局路由守卫

  • 全局前置守卫router.beforeEach
  • 全局解析守卫:router.beforeResolve
  • 全局后置守卫:router.afterEach
beforeEach(to,from, next)

在路由跳转前触发,参数包括to,from,next 三个,这个钩子作用主要是用于登录验证。

前置守卫也可以理解为一个路由拦截器,也就是说所有的路由在跳转前都要先被前置守卫拦截。


router.beforeEach(async (to, from, next) => {
  // 清除面包屑导航数据
  store.commit('common/SET_BREAD_NAV', [])
  // 是否白名单
  if (isWhiteList(to)) {
    next()
  } else {
    // 未登录,先登录
    try {
      if (!store.state.user.userInfo) {
        await store.dispatch('user/getUserInfo')
        // 登录后判断,是否有角色, 无角色 到平台默认页
        if (!store.state.user.userInfo.permissions || !store.state.user.userInfo.permissions.length) {
          next({ path: '/noPermission' })
        }
      }

      // 登录后判断,是否有访问页面的权限
      if (!hasVisitPermission(to, store.state.user.userInfo)) {
        next({ path: '/404' })
      } else {
        next()
      }
    } catch (err) {
      $error(err)
    }
  }
})
beforeResolve(to,from, next)

在每次导航时都会触发,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。

即在 beforeEach 和 组件内 beforeRouteEnter 之后,afterEach之前调用

router.beforeResolve 是获取数据或执行任何其他操作的理想位置

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})
afterEach(to,from)

和beforeEach相反,他是在路由跳转完成后触发,参数包括to, from 由于此时路由已经完成跳转 所以不会再有next。

全局后置守卫对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。

router.afterEach((to, from) => {
	// 在路由完成跳转后执行,实现分析、更改页面标题、声明页面等辅助功能
	sendToAnalytics(to.fullPath)
})

独享路由守卫

beforeEnter(to,from, next) 独享路由守卫可以直接在路由配置上定义,但是它只在进入路由时触发,不会在 params、query 或 hash 改变时触发

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    // 在路由配置中定义守卫
    beforeEnter: (to, from,next) => {
      next()
    },
  },
]

或是使用数组的方式传递给 beforeEnter ,有利于实现路由守卫的重用

function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

组件路由守卫

在组件内使用的钩子函数,类似于组件的生命周期, 钩子函数执行的顺序包括

  • beforeRouteEnter(to,from, next) -- 进入前
  • beforeRouteUpdate(to,from, next) -- 路由变化时
  • beforeRouteLeave(to,from, next) -- 离开后

组件内路由守卫的执行时机:


<template>
  ...
</template>
export default{
  data(){
    //...
  },
  
  // 在渲染该组件的对应路由被验证前调用
  beforeRouteEnter (to, from, next) {
    // 此时 不能获取组件实例 this
    // 因为当守卫执行前,组件实例还没被创建
    next((vm)=>{
      // next 回调 在 组件 beforeMount 之后执行 此时组件实例已创建,
      // 可以通过 vm 访问组件实例
      console.log('A组件中的路由守卫==>> beforeRouteEnter 中next 回调 vm', vm)
    )
  },

  // 可用于检测路由的变化
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用  此时组件已挂载完可以访问组件实例 `this`
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    console.log('组件中的路由守卫==>> beforeRouteUpdate')
    next()
  },

  // 在导航离开渲染该组件的对应路由时调用
  beforeRouteLeave (to, from, next) {
    // 可以访问组件实例 `this`
    console.log('A组件中的路由守卫==>> beforeRouteLeave')
    next()
  }
}
<style>
...
</style>

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持 传递回调,因为没有必要了

路由守卫触发流程

页面加载时路由守卫触发顺序:

  • 触发全局的路由守卫 beforeEach
  • 组件在路由配置的独享路由 beforeEnter
  • 进入组件中的 beforeRouteEnter,此时无法获取组件对象
  • 触发全局解析守卫 beforeResolve
  • 此时路由完成跳转 触发全局后置守卫 afterEach
  • 组件的挂载 beforeCreate --> created --> beforeMount
  • 路由守卫 beforeRouterEnter 中的 next回调, 此时能够获取到组件实例 vm
  • 完成组件的挂载 mounted

当点击切换路由时: A页面跳转至B页面触发的生命周期及路由守卫顺序:

  • 导航被触发进入其他路由。
  • 在离开的路由组件中调用 beforeRouteLeave 。
  • 调用全局的前置路由守卫 beforeEach 。
  • 在重用的组件里调用 beforeRouteUpdate 守卫。
  • 调用被激活组件的路由配置中调用 beforeEnter。
  • 解析异步路由组件。
  • 在被激活的组件中调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫。
  • 导航被确认。
  • 调用全局后置路由 afterEach 钩子。
  • 触发 DOM 更新,激活组件的创建及挂载 beforeCreate (新)-->created (新)-->beforeMount(新) 。
  • 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
  • 失活组件的销毁 beforeDestory(旧)-->destoryed(旧)
  • 激活组件的挂载 mounted(新)

当路由更新时:触发 beforeRouteUpdate

注意: 但凡涉及到有next参数的钩子,必须调用next() 才能继续往下执行下一个钩子,否则路由跳转等会停止。

vueRouter 实现原理

vueRouter 实现的原理就是 监听浏览器中 url 的 hash值变化,并切换对应的组件

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

safe030

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

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

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

打赏作者

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

抵扣说明:

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

余额充值