《前端攻城狮 · 从 Nuxt 前端框架开篇》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍

写在前面的话

近些年,前端技术栈悄然发生翻天覆地的变化,各类前端技术的变革换代频率已不低于后端。
依稀记得10多年前,宣传标语还是玩转jQuery者得天下,到后面的Vue、Angular、React三足鼎立,再到后来NodeJS横空出世,带出了大前端的概念,紧接着Express、Vite、Nuxt等前端框架相继抢占一席之地,当然,前端技术栈远不止如此,还有Webpack、Axios、Promise等等。前端程序猿再也不能称之为“切图仔”,而应给予不弱于后端程序猿的地位。
嗯,扯远了,关于前端的发展历史,源远流长,可以聊个半天,后续再另外开篇博客唠嗑,回到正题。
笔者所在公司一直坚持前后端分离模式,先采用Express框架,后采用Nuxt框架,本篇博客就以Nuxt框架的简介,作为前端系列文章的开篇,先简单介绍,后续会深入介绍实战部分,当然也会持续完善该前端专栏。


基础入门

模板启动

参考:官网 - 安装

Tips:看步骤如此之简单,其实也不难。
Tips:网上很多教程是针对Nuxt3的,要注意一下Nuxt2部分用不了。

Step1、npx create-nuxt-app demo
Step2、npm run dev
Step3、http://localhost:3000/

目录结构

Nuxt.js 的默认应用目录架构提供了良好的代码分层结构,适用于开发或大或小的应用。当然,你也可以根据自己的偏好组织应用代码。

1、资源目录 assets
用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。

**2、组件目录 components **
用于组织应用的 Vue.js 组件,Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性。

Tips:存放普通的组件,非布局和路由组件。

3、布局目录 layouts
用于组织应用的布局组件。

Tips:公用的外框,顶部、左侧、底部等等,可定义默认的,或者自定义的布局。

4、中间件目录 middleware
用于存放应用的中间件。

Tips:类似拦截器的作用,可充当路由前置守卫,例如权限校验、前置设值等等。

5、页面目录 pages
用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。
若无额外配置,该目录不能被重命名。

Tips:生成的路由配置在.nuxt/router.js中,这个也是Nuxt最强大的特征之一。

**6、插件目录 plugins **
用于组织那些需要在根vue.js应用实例化之前需要运行的 Javascript 插件。

Tips:可以是第三方或自定义的,和Vue脚手架的用法差不多,增强Vue的功能,一般用Vue.use绑定,然后vm和vc实例可以增加方法。

**7、静态文件目录 static **
用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。
若无额外配置,该目录不能被重命名。

Tips:例如这样可以访问static下面的图片,http://localhost:3000/favicon.ico

8、Store 目录
用于组织应用的 Vuex 状态树 文件。 Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。
若无额外配置,该目录不能被重命名。

Tips:Nuxt 简化了 Vuex 的配置,不过基础用法差不多。

9、nuxt.config.js 文件
nuxt.config.js 文件用于组织 Nuxt.js 应用的个性化配置,以便覆盖默认配置。
若无额外配置,该文件不能被重命名。

10、package.json 文件
package.json 文件用于描述应用的依赖关系和对外暴露的脚本接口。
该文件不能被重命名。

11、其他文件

  • node_modules:存放npm install的安装的所有依赖包
  • .nuxt:在npm run dev启动项目的时候自动生成的临时文件 类似于vue的dist
  • .eslintrc.js:ESLint的配置规则文件
  • .gitignore:Git在提交时,不上传到仓库的文件

实战使用

路由

点评:Nuxt的路由简化配置是一大亮点,只要掌握了 Vue-Router 的用法,这里并不复杂。

Tips:这里就不特殊展开了,有需要在Demo项目练手,定义、传参、跳转能掌握即可。

使用范例:

<nuxt-link :to="{name: 'about', params:{ id1: '123'}, query:{ id2: '234'}}">跳转</nuxt-link>

<!-- 对应接收 -->                                                                                     
id1:{{ $route.params.id1 }}
id2:{{ $route.query.id2 }}

动态路由:

<nuxt-link to="/user/123">前往详情</nuxt-link>

<!-- 可以配置参数校验 -->  
validate({ params }) {
  // 如果校验方法返回的值不为 true或Promise中 resolve 解析为false或抛出 Error
  //Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面。
  return /^\d+$/.test(params.id)
}

image.png

注意事项:
1、使用 nuxt-link 标签,而不是 a 标签 或者 router-link;
2、由于是多页面应用,页面是直接跳转的,因此也不需要 vue 的 router-view;
3、路由可以配置动画效果,就添加一下CSS即可,参考官网和Demo项目;


布局

参考:官网 - 布局
Nuxt.js 允许你扩展默认的布局,或在 layouts 目录下创建自定义的布局。
布局的大部分行为和页面差不多,这里不特殊展开。

Tips:简单的功能,平时也都用得上,即平时说的页面外框,常见于顶部、底部、左侧菜单栏。

image.png

【默认布局】
可通过添加 layouts/default.vue 文件来扩展应用的默认布局。
通过这种方式添加的,页面如果没特殊配置,都会用到它,这里要注意了!

<template>
  <div>
    <h1>默认布局 - 顶部的信息</h1>
    <hr>
    <nuxt/>
  </div>
</template>

【自定义布局】
layouts 目录中的每个文件 (顶级) 都将创建一个可通过页面组件中的 layout 属性访问的自定义布局。

<!-- /layouts/lwLayout.vue -->
<template>
  <div>
    <h1>自定义布局 - 顶部的信息</h1>
    <hr>
    <nuxt/>
  </div>
</template>

<!-- 具体页面 -->
<script>
export default {
  name: 'IndexPage',
  layout: 'lwLayout'
}
</script>

【自定义异常页面】
可以在布局目录创建一个 error.vue 页面,可以替换系统默认的异常信息显示页。

<template>
  <div class="page">
    <h3>这是自定义的异常页面,错误信息如下:</h3>
    {{error}}
  </div>
</template>
<script>
export default {
  props: {
    error: {
      type: [Object, Array],
      default: () => {
      },
    },
  },
  mounted() {

  }
}
</script>
<style scoped>
.page {
  width: 100%;
  height: 100%;
}
</style>

【默认模板】
这个和布局不是一个概念,在根目录创建app.html可以实现覆盖,但一般用不上,不展开说明。


中间件

中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。
每一个中间件应放置在 middleware/ 目录,middleware/auth.js 将成为 auth 中间件。
参考:中间件(middleware)详解

Tips:有点类似前置拦截器的作用,前置路由守卫做的事情差不多。

【中间件的定义示例】

export default function ({route, req, res, store, redirect, next, $axios}) {

  let isClient = process.client;
  let isServer = process.server;
  if (isClient) {
    //store.commit('INIT_WEB')
    console.log('====当前为客户端访问')
  }
  if (isServer) {
    //store.commit('GET_TOKEN_SERVER', req)
    console.log('====当前为服务端访问')
  }

  $axios.get('https://zhanshen666.mynatapp.cc/').then((res)=> console.log(res.data));

  console.log(`====当前路由地址为${route.fullPath}`)

  if (route.fullPath === "/aboutTemp") {
    return redirect({path: '/about'})
  }

  console.log(store.state.user.userNum)
  if (store.state.user.userNum !== 0) {
    return redirect('/login')
  }
}

Tips:navigateTo 和 abortNavigation 是 Nuxt3 的用法,Nuxt2 老老实实使用redirect即可。

【中间件的指定示例】
1、nuxt.config.js 中全局指定,例如下面这样配置,stats 中间件将在每个路由改变时被调用。

router: {
  middleware: 'middle'
}

2、将 middleware 添加到指定的布局或者页面,实现局部配置。

export default {
  middleware: 'middle'
}

Tips:有执行顺序,nuxt.config.js - 匹配布局 - 匹配页面。

【中间件可能的返回值】

无返回值:也就是说当前中间件不会阻塞路由跳转。
return navigateTo('/')or return navigateTo({ path: '/' }), 重定向到指定的路径,如何是在服务端的话,会设置 redirect code 为302
return navigateTo('/', { redirectCode: 301 }), 重定向到指定的路径,如果直服务端的话,会设置 redirect code 为301 表示这个重定向的永久的。
return abortNavigation()终止当前的跳转
return abortNavigation(error)终止跳转并带有错误信息

动态添加中间件】
通过addRouteMiddleware()方法可以在代码中动态添加全局和命名路由中间件。
例如在插件中

export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('this global middleware was added in a plugin and will be run on every route change')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('this named middleware was added in a plugin and would override any existing middleware of the same name')
  })
})

插件

Nuxt.js 允许您在运行 Vue.js 应用程序之前执行 js 插件。这在您需要使用自己的库或第三方模块时特别有用。

Tips:和Vue的插件用法差不多,都是增强Vue的能力,然后可以使用this.$xxx操作。
Tips:凡是修改配置文件,都需要重启服务。

参考:官网 - 插件

【使用第三方模块】
我们可以在应用中使用第三方模块,一个典型的例子是在客户端和服务端使用 axios 做 HTTP 请求。
首先我们需要安装 npm 包:
npm install --save axios
然后,在页面内这样使用:

<template>
  <h1>{{ title }}</h1>
</template>

<script>
  import axios from 'axios'

  export default {
    async asyncData({ params }) {
      let { data } = await axios.get(`https://my-api/posts/${params.id}`)
      return { title: data.title }
    }
  }
</script>

【自定义插件】
参考如下代码即可,很简单。
第一个参数是上下文,里面有很多内容,比如可以操作store等。

/**
 * 自定义插件
 * Step1、定义本插件,即本文件,plugins/pluginTest.js
 * Step2、nuxt.config.js配置plugins,{ src: '~/plugins/pluginTest' }
 * Step3、页面使用插件,this.$lw.consoleHandle
 */
export default ({app, store}, inject) => {
  inject('lw', {
    consoleHandle({content = ''}) {
      console.log(content)
      //store.commit('lw/handle', { content })
    }
  })
}

【原始Vue的方式引入插件】
同样也需要在nuxt.config.js配置plugins,使用就直接vc对象通过this操作。

import Vue from 'vue'

const storage = {

  install(Vue) {
    
    /**
     * @params key setItem key
     * @params value setItem value
     * @params expire 过期时间<number> 默认7
     */
    Vue.prototype.$setStorage = (key, value, expire = 7) => {
      let obj = {
        value, time: Date.now(), expire: expire * 60 * 60 * 24
      }
      localStorage && localStorage.setItem(key, JSON.stringify(obj))
    }

    Vue.prototype.$getStorage = (key) => {
      let val = localStorage.getItem(key)
      if (!val) return null
      val = JSON.parse(val)
      // 过期
      if ((Date.now() - val.time) > val.expire) {
        localStorage.removeItem(key)
        return null
      }
      return val.value
    }

    Vue.prototype.$delStorage = (key) => {
      localStorage.removeItem(key)
    }
  }
}
Vue.use(storage)


静态文件

如果你的静态资源文件需要 Webpack 做构建编译处理,可以放到 assets 目录,否则可以放到 static 目录中去。
Nuxt 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下,像 robots.txt 或 sitemap.xml 这种类型的文件就很适合放到 static 目录中。
你可以在代码中使用根路径 / 结合资源相对路径来引用静态资源:

<!-- 引用 static 目录下的图片 -->
<img src="/my-image.png" />

<!-- 引用 assets 目录下经过 webpack 构建处理后的图片 -->
<img src="~/assets/my-image-2.png" />

image.png

【关于别名】
或 @ 代表 srcDir
提示:在您的 vue 模板中, 如果你需要引入 assets 或者 static 目录, 使用 ~/assets/your_image.png 和 ~/static/your_image.png方式。

Tips:经测试两个都可用,~后面是否加/也不影响。


Vuex

功能描述
Nuxt.js 内置引用了 vuex 模块,所以不需要额外安装。
Nuxt.js 会尝试找到应用根目录下的 store 目录,如果该目录存在,它将做以下的事情:
1、引用 vuex 模块
2、将 vuex 模块 加到 vendors 构建配置中去
3、设置 Vue 根实例的 store 配置项
Nuxt.js 支持两种使用 store 的方式,你可以择一使用:
1、普通方式: store/index.js 返回一个 Vuex.Store 实例
2、模块方式: store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)
参考:Vuex学习,较详细用法
参考:Vuex状态管理详解

【实际案例】

/**
 * Nuxt 使用 store
 * Step1、store目录中新建user.js,直接暴露state等属性
 * Step2、不需要其他配置了
 */

/**
 * state 就是数据,普通的对象
 * vuex是单一状态树,仅用一个对象就包含了全部的应用层级状态,所有需要集中管理的数据都要放到State中存储。
 * 页面获取方式:$store.state 或 computed 中的 mapState方式,例如:this.$store.state.user.userNum
 */
export const state = () => ({
  userNum: 0, userInfo: null
})

/**
 * getter 是计算属性,返回对state数据的装饰,相当于vue中的computed,可以针对state的做一些格式转换等简单操作
 * getter 非必须,没有额外的操作可以直接获取 state
 * 页面获取方式:$store.getters 或 computed 中的 mapGetters,例如:this.$store.getters["user/G_USER_NUM"]
 */
export const getters = {

  G_USER_INFO(state) {
    return state.userInfo
  },

  G_USER_NUM(state) {
    return state.userNum
  }
}

/**
 * mutation 是唯一提交更改stare中状态的方法,类似Spring中的Dao层
 * mutation 一般是必须的,不可省略
 * 页面获取方式:$store.commit 或 methods 中的 mapMutatuins,例如:this.$store.commit('user/M_SET_USER_NUM', '123')
 */
export const mutations = {

  M_SET_USER(state, data) {
    state.userInfo = data
  },

  M_SET_USER_NUM(state, data) {
    state.userNum = data
  }
}

/**
 * action 类似于 mutation,不过可以做一些额外操作,类似Spring中的Controller和Service
 * action 提交的是 mutation,而不是直接变更状态
 * action 非必须,如果没有特别的操作,也可以直接操作 mutation
 * 页面获取方式:$store.commit 或 methods 中的 mapActions,例如:this.$store.commit('user/M_SET_USER_NUM', '123')
 */
export const actions = {

  A_SET_USER({commit, $axios}, data) {
    console.log('执行一下A_SET_USER操作');
    //Action 通常是异步的
    return new Promise((resolve, reject) => {
      //两秒后执行addUserCount方法
      setTimeout(() => {
        commit('M_SET_USER', data)
        resolve()
      }, 2000)
    })
  },

  A_SET_USER_NUM(context, data) {
    console.log('执行一下A_SET_USER_NUM操作');
    //注意,actions只能异步修改state数据,如果要马上获取值,尽量回调里面处理数据,这里就算没有setTimeout也如此
    //一秒后执行addMessageCount方法
    setTimeout(() => {
      //context是vue的执行上下文,可以理解为this
      context.commit('M_SET_USER_NUM')
    }, 1000)
  }
}

export default {
  state, getters, actions, mutations
}

testVuex() {
  this.$store.commit('user/M_SET_USER_NUM', '123')
  let x1 = this.$store.getters["user/G_USER_NUM"]
  console.log(x1)
  this.$store.dispatch('user/A_SET_USER', {name: '123'}).then((res) => {
    console.log(this.$store.state.user.userInfo)
  })
},  

AsyncData

参考:官网 - 异步数据

理论说明
Nuxt 扩展了 Vue,增加了一个叫 asyncData 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。
1、可以理解为一个新的生命周期钩子,用于服务端获取数据,可以返回数据拼接到data中。
2、asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。
3、由于asyncData方法是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。

几个注意点
1、页面级组件中服务端才会生效的钩子
2、页面刷新时不执行
3、return出去的对象会放入到客户端中的data数据中

代码示例
Nuxt.js 提供了几种不同的方法来使用 asyncData 方法,你可以选择自己熟悉的一种来用:、
1、返回一个 Promise, nuxt.js 会等待该Promise被解析之后才会设置组件的数据,从而渲染组件.
2、使用 async 或 await ;
示例如下,context可以选择解构:

	/**
   * 异步获取数据的钩子
   * @param app 可以理解为全局对象
   * @param params 获取参数
   * @param query 获取参数
   * @param error 调用该方法来显示错误信息页面。params.statusCode 可用于指定服务端返回的请求状态码。
   * @param route 路由对象,包含路径,参数等信息
   * @param $axios 远程调用
   * @param req 可以获取请求头信息
   * @returns {*}
   */
  async asyncData({app, params, query, error, route, $axios, req}) {

    console.log('输出参数:', params, query, route)

    // 请检查您是否在服务器端
    if (process.server) {
      app.handleApp('战神刘玉栋!')
      app.$myInjectedFunction(req.headers.host)
    }

    //写法一,搭配async
    let res = await $axios.get('https://zhanshen666.mynatapp.cc/')

    //同时请求两个
    const [res1, res2] = await Promise.all([
      $axios.$get('https://zhanshen666.mynatapp.cc/'),
      $axios.$get('https://zhanshen666.mynatapp.cc/'),
    ])
    console.log('Promise.all输出', res1, res2)
    return {msg: res.data}

    //写法二,不搭配async
    /*return $axios.get('https://zhanshen666.mynatapp.cc/')
      .then((res) => {
          return {msg: res.data}
        }
      )
      .catch(e => {
        error({ statusCode: 404, message: 'Post not found 哈哈' })
      })*/
  }

【nuxtServerInit】

Tips:这部分还没实操。

1、获取服务端数据
2、可访问到服务端的context对象
3、只存在于vuex中
nuxt会在每一次请求服务器页面时执行,即:首次进入页面或刷新页面,且只存在于vuex中的action对象中
// vuex中记录获取cookie记录登录状态
export const actions = {
nuxtServerInit({ commit }, { req }) {
try {
let cookies = req.headers[‘cookie’]
console.log(cookies)
} catch (error) {
console.log(error)
}
}
}


上下文对象

很关键的概念,里面能获取到很多信息,在Nuxt的几个核心用法中,都可以拿到,例如asyncData、插件、中间件。
注意,如果没加async的话,可以debugger,但是部分内容,F12是看不到的,比如req,属于服务端的。

	/**
   * 异步获取数据的钩子
   * @param app 可以理解为全局对象
   * @param params 获取参数
   * @param query 获取参数
   * @param error 调用该方法来显示错误信息页面。params.statusCode 可用于指定服务端返回的请求状态码。
   * @param route 路由对象,包含路径,参数等信息
   * @param $axios 远程调用
   * @param req 可以获取请求头信息
   * @param res 用的较少
   * @returns {*}
   */
  asyncData({app, params, query, error, route, $axios, req, res}) {

    console.log('输出参数:', params, query, route)

    // 请检查您是否在服务器端
    if (process.server) {
      app.handleApp('战神刘玉栋!')
      app.$myInjectedFunction(req.headers.host)
    }

    //写法一,搭配async
    /*let res = await $axios.get('https://zhanshen666.mynatapp.cc/')
    return {msg: res.data}*/

    //写法二,不搭配async
    return $axios.get('https://zhanshen666.mynatapp.cc/')
      .then((res) => {
          return {msg: res.data}
        }
      )
      .catch(e => {
        error({ statusCode: 404, message: 'Post not found 哈哈' })
      })
  }

常用配置

Tips:指的是 nuxt.config.js 和 package.json 的相关配置。

【配置启动地址】

"config": {
  "nuxt": {
    "host" : "127.0.0.1",
    "port" : "3003"
  }
},

【配置全局CSS】

css: ['assets/main.css'],

【引入CSS和JS】
全局的就在nuxt.config.js中添加,单页就在vue页面的head添加,如下:
image.png
image.png
参考:https://blog.csdn.net/Tomwildboar/article/details/100184400

【动态设置页面title及meta信息】
image.png


外部拓展

引入 Axios

Tips:这里使用官方推荐的@nuxtjs/axios,模板项目创建的时候也会集成,当然,也可以使用原生的。

Step1、下载依赖
npm install @nuxtjs/axios
Step2、nuxt.config.js 添加相应配置

module.exports = {
  modules: [
    '@nuxtjs/axios',
  ],
  axios: {
    //baseURL:'http://localhost:10010/'

  }
}

Step3、具体使用
按上述配置后,$axios 对象会被绑定到实例原型,使用方式和原生Vue中一样,VM和VC实例可以正常操作这个,中间件和asyncData的上下文也可以拿到。

export default {
  methods: {
    async doRequest() {
      const res = await this.$axios.get('/xxxx')
    }
  },
  async asyncData(context) {
    // 发送ajax
    let {data} = await context.$axios.get('/service-consumer/feign/echo/abc')
    // 返回数据
    return {
      echo: data
    }
  },
}

注意:下载依赖卡住,可以检查一下NPM仓库。
npm config set registry https://registry.npmmirror.com/

关于@nuxtjs/axios
1、这是官方推荐的axios库,由于在nuxt.config中已经配置过,所以在页面中,可以直接使用。
2、在组件内通过this. a x i o s 即可使用。 3 、在 a s y n c D a t a 方法中获取 a x i o s ,有一个上下文参数,可以通过这个参数 c o n t e x t . axios即可使用。 3、在asyncData方法中获取axios,有一个上下文参数,可以通过这个参数context. axios即可使用。3、在asyncData方法中获取axios,有一个上下文参数,可以通过这个参数context.axios,或通过解构的方式获取


引入外部文件

1、所有页面引入:在nuxt.config文件中,head属性中进行配置,所有页面都会引入这些文件
2、单个页面引入:在页面组件中,定义head方法即可。

Tips:局部权限高于全局。

head() {
  return {
    title: 'Nuxt牛逼',
    meta: [
          {
              hid: 'description',
              name: 'description',
              content: '天苍苍野茫茫'
          }
    ],
   link: [ // 这里可以引入css样式
       { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ],
   script: [
       { src: 'https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js'}
   ]
  }
}

引入 js-cookie

通常搭配鉴权中间件使用。
参考:middleware 实现鉴权


引入百度统计代码

新建plugins/baidu.js

export default ({app: {router}, store}) => {
  /* 每次路由变更时进行pv统计 */
  router.afterEach((to, from) => {
    /* 告诉增加一个PV */
    try {
      window._hmt = window._hmt || []
      window._hmt.push(['_trackPageview', to.fullPath])
    } catch (e) {
    }
  })
}

nuxt.config.js中配置plugins字段

script: [
  { src: 'https://hm.baidu.com/hm.js?****' }
]

开启https

nuxt.config.js中的server字段

const path = require('path')
const fs = require('fs')
module.exports = {
  server: {
    https: {
      // 此处路径可以直接写死 读取https证书文件
      // key: fs.readFileSync(path.resolve(__dirname, '**server.key'))
      key: fs.readFileSync('/usr/local/***.key')
    }
  }
}

总结陈词

上文简单介绍了Nuxt框架,这里篇幅受限,没有深入介绍公司关于Nuxt的一些特性,后续专栏将以其他形式继续收录和总结。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

战神刘玉栋

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

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

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

打赏作者

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

抵扣说明:

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

余额充值