Vue项目实战---外卖app笔记

day01

1. 项目开发准备

项目描述
技术选型
API接口
你能从此项目中学到什么?

2. 开启项目开发

使用脚手架创建项目
安装所有依赖/指定依赖
开发环境运行
生产环境打包与发布

3. 搭建项目整体界面结构

stylus的理解和使用
    结构化, 变量, 函数/minxin(混合)
vue-router的理解和使用
    router-view/router-link/keep-alive
    $router: 路由器对象, 包含一些操作路由的功能函数, 来实现编程式导航(跳转路由)
    $route: 当前路由对象, 一些当前路由信息数据的容器, path/meta/query/params
项目路由拆分
底部导航组件: FooterGuide
导航路由组件: Msite/Search/Order/Profile

4. 抽取组件

头部组件: HeaderTop, 通过slot来实现组件通信标签结构
商家列表组件: ShopList

5. 登陆路由组件

 静态组件
 FooterGuide的显示/隐藏: 通过路由的meta

6. 后台项目

启动后台项目: 理解前后台分离
测试后台接口: 使用postman
修正接口文档

7. 前后台交互

ajax请求库: axios
ajax请求函数封装: axios + promise
接口请求函数封装: 每个后台接口

day02

1. 异步数据

封装ajax: 
    promise+axios封装ajax请求的函数
    封装每个接口对应的请求函数(能根据接口定义ajax请求函数)
    解决ajax的跨越域问题: 配置代理, 对代理的理解
vuex编码
    创建所有相关的模块: store/index|state|mutations|actions|getters|mutation-types
    设计state: 从后台获取的数据
    实现actions: 
        定义异步action: async/await
        流程: 发ajax获取数据, commit给mutation
    实现mutations: 给状态赋值
    实现index: 创建store对象
    main.js: 配置store
组件异步显示数据
    在mounted()通过$store.dispatch('actionName')来异步获取后台数据到state中
    mapState(['xxx'])读取state中数据到组件中
    在模板中显示xxx的数据
模板中显示数据的来源
    data: 自身的数据(内部改变)
    props: 外部传入的数据(外部改变)
    computed: 根据data/props/别的compute/state/getters
异步显示轮播图
    通过vuex获取foodCategorys数组(发请求, 读取)
    对数据进行整合计算(一维变为特定的二维数组)
    使用Swiper显示轮播, 如何在界面更新之后创建Swiper对象?
        1). 使用回调+$nextTick()
        2). 使用watch+$nextTick()	

2. 登陆/注册: 界面相关效果

a. 切换登陆方式
b. 手机号合法检查
c. 倒计时效果
d. 切换显示或隐藏密码
g. 前台验证提示

3. 前后台交互相关问题

1). 如何查看你的应用是否发送某个ajax请求?  
    浏览器的network
2). 发ajax请求404
    请求的路径的对
    代理是否生效(配置和重启)
    服务器应用是否运行
3). 后台返回了数据, 但页面没有显示?
    vuex中是否有
    组件中是否读取

day03

1. 完成登陆/注册功能

1). 2种方式
   手机号/短信验证码登陆
   用户名/密码/图片验证码登陆
2). 登陆的基本流程
   表单前台验证, 如果不通过, 提示
   发送ajax请求, 得到返回的结果
   根据结果的标识(code)来判断登陆请求是否成功
       1: 不成功, 显示提示
       0. 成功, 保存用户信息, 返回到上次路由
3). vue自定义事件
   绑定监听: @eventName="fn"  function fn (data) {// 处理}
   分发事件: this.$emit('eventName', data)
4). 注意:
   使用network查看请求(路径/参数/请求方式/响应数据)
   使用vue的chrome插件查看vuex中的state和组件中的数据
   使用debugger语句调试代码
   实参类型与形参类型的匹配问题

2. 搭建商家整体界面

1). 拆分界面路由
2). 路由的定义/配置|使用

3. 模拟(mock)数据/接口

1). 前后台分离的理解
2). mockjs的理解和使用
3). jons数据设计的理解

4. ShopHeader组件

1). 异步显示数据效果的编码流程
    ajax
      ajax请求函数
      接口请求函数
    vuex
      state
      mutation-types
      actions
      mutations
    组件
      dispatch(): 异步获取后台数据到vuex的state
      mapState(): 从vuex的state中读取对应的数据
      模板中显示
2). 初始显示异常
    情况1: Cannot read property 'xxx' of undefined"
    原因: 初始值是空对象, 内部没有数据, 而模块中直接显示3层表达式
    解决: 使用v-if指令
    
    情况2: Cannot read property 'xxx' of null"
 
3). vue transition动画

day04

1. ShopGoods组件

1). 动态展现列表数据
2). 基本滑动:
    使用better-scroll
    理解其基本原理
    创建BScroll对象的时机
      watch + $nextTick()
      callback + $nextTick
3). 滑动右侧列表, 左侧同步更新
    better-scroll禁用了原生的dom事件, 使用的是自定义事件
    绑定监听: scroll/scrollEnd
    滚动监听的类型: probeType
    列表滑动的3种类型
        手指触摸
        惯性
        编码
    分析:
        类名: current 标识当前分类
        设计一个计算属性: currentIndex
        根据哪些数据计算?
          scrollY: 右侧滑动的Y轴坐标 (滑动过程时实时变化)
          tops: 所有右侧分类li的top组成的数组  (列表第一次显示后就不再变化)
    编码:
        1. 在滑动过程中, 实时收集scrollY
        2. 列表第一次显示后, 收集tops
        3. 实现currentIndex的计算逻辑
4). 点击左侧列表项, 右侧滑动到对应位置

2. CartControl组件

1). 问题: 更新状态数据, 对应的界面不变化
    原因: 一般方法给一个已有绑定的对象中添加一个新的属性, 这个属性没有数据绑定
    解决: 
        Vue.set(obj, 'xxx', value)才有数据绑定
        this.$set(obj, 'xxx', value)才有数据绑定

3. ShopCart组件

1). 使用vuex管理购物项数据: cartFoods
2). 解决几个功能性bug

4. Food组件

1). 父子组件:
    子组件调用父组件的方法: 通过props将方法传递给子组件
    父组件调用子组件的方法: 通过ref找到子组件标签对象

day05

1. ShopRatings组件

1). 列表的过滤显示
2). 自定义过滤器

2. ShopInfo组件

1). 使用better-scroll实现两个方向的滑动
1). 通过JS动态操作样式
2). 解决当前路由刷新异常的bug

3. Search组件

1). 根据关键字来异步搜索显示匹配的商家列表
2). 如实实现没有搜索结果的提示显示

4. 项目优化

1). 缓存路由组件对象
2). 路由组件懒加载
3). 图片司加载: vue-lazyload
4). 分析打包文件并优化 

1 vue-router知识补充

「vue-router概念」

​ 这里的路由并不是指我们平时所说的硬件路由器,这里的路由就是SPA(single page application单页应用)的路径管理器。再通俗的说,vue-router就是WebApp的链接路径管理系统。

​ vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起url和页面之间的映射关系。

​ (vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。路由实际上就是可以理解为指向,就是我在页面上点击一个按钮需要跳转到对应的页面,这就是路由跳转)

「为什么不用a标签」

​ 至于我们为啥不能用a标签,这是因为用Vue做的都是单页应用,就相当于只有一个主的index.html页面,所以你写的标签是不起作用的,你必须使用vue-router来进行管理。

「vue-router作用」

img

2 API接口(前后台交互API接口)

​ API接口的四个组成部分:请求三个(url,method,data请求参数格式)、响应一个

​ 接口文档:所有接口的描述信息 、 对接口、联调、前后台分离、mock数据(模拟数据)

3 实战目的

​ 1) 熟悉一个项目的开发流程
​ 2) 学会模块化、组件化、工程化的开发模式
​ 3) 掌握使用vue-cli 脚手架初始化Vue.js 项目
​ 4) 学会模拟json 后端数据,实现前后端分离开发
​ 5) 学会ES6+eslint 的开发方式
​ 6) 掌握一些项目优化技巧

「vue插件的使用」

​ 1) 学会使用vue-router 开发单页应用
​ 2) 学会使用axios/vue-resource 与后端进行数据交互
​ 3) 学会使用vuex 管理应用组件状态
​ 4) 学会使用better-scroll/vue-scroller 实现页面滑动效果
​ 5) 学会使用mint-ui 组件库构建界面
​ 6) 学会使用vue-lazyload 实现图片惰加载
​ 7) 学会使用mockjs 模拟后台数据接口

「编码测试与打包发布项目」

image-20210523211450612

「相关概念」

​ 1) 标注图(设计稿): 对应用界面各个组成元素进行坐标/大小/颜色等进行标签的界面图

​ 2) 切图: 将应用界面的一些静态图形部分, 通过工具(如photoshop)剪裁生成的图片
​ 3) 图片Base64: 样式中引用的小图片, 在webpack 打包会自动处理转换为样式内部的Base64 编码字符串
​ 4) 2x 与3x 图: 不同手机的屏幕密度不一样, 一般都在2 以上(如iphone6 为2,iphone6s 为3), 为了适配不同的手 机, UI 设计师为同一个图片制作了2x 和3x 的2 套图片(图形一样,但大小不一样)

「iconfont字体图标」

​ 三种引用方式:unicode 引用、symbol 引用、font-class 引用
​ 在页面中引入在线的iconfont 样式

<link rel="stylesheet"
href="http://at.alicdn.com/t/font_518606_6676bmcalnrhehfr.css">

4 项目源码目录设计

image-20210523204329780

「应用的整体vue组件结构」
src

​ |-- components------------非路由组件文件夹
​ |-- FooterGuide---------------底部组件文件夹
​ |-- FooterGuide.vue--------底部组件vue
​ |-- pages-----------------路由组件文件夹
​ |-- Msite---------------首页组件文件夹
​ |-- Msite.vue--------首页组件vue
​ |-- Search----------------搜索组件文件夹
​ |-- Search.vue---------搜索组件vue
|-- Order--------------订单组件文件夹
​ |-- Order.vue-------订单组件vue
​ |-- Profile--------------个人组件文件夹
​ |-- Profile.vue-------个人组件vue
​ |-- App.vue---------------应用根组件vue
​ |-- main.js---------------应用入口js

「后台应用」

​ mongodb服务

​ https://blog.csdn.net/weixin_45844049/article/details/104761108

「Git对项目进行同步」

​ 补充Git的相关知识

本地分支   创建本地分支,在需要打包的文件根目录下   git init --> git add *  --> git commit -m “message"
远程跟踪分支(remote/分支名)      在GitHub上新建仓库,复制关联代码  git remote add origin xxx 到本地运行
远程分支 						git push oringin master  (先关联再推送)
「手机号合法性检查」

​ 利用正则表达式验证

  computed:{
      rightPhone(){
        return /^1\d{10}$/.test(this.phone)
      }
「vue动态绑定class」
 <div class="switch_circle" :class="{right:showPwd}"></div>  // 利用对象方式动态绑定class
为什么在项目中data需要使用return返回数据呢

​ 不使用return包裹的数据会在项目的全局可见,会造成变量污染;使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。

const {comment,index,deleteComment} = this 
上面的这句话是一个简写,最终的含义相当于
const  comment = this.comment
const  index = this.index
const   deleteComment = this.deleteComment
Vue生命周期中mounted和created的区别

​ created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。

​ mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

「 vue动画效果模块-- transition」

<transition> 元素作为单个元素/组件的过渡效果。<transition> 只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中。

「常见的src目录结构」

​ assets: 放置静态资源,包括公共的 css 文件、 js 文件、iconfont 字体文件、img 图片文件 以及其他资源类文件。之所以强调是公共的 css 文件,是因为要在组件的 css 标签里加入 ‘scoped‘ 标记,将其作用范围限制在此组件以及调用它的父级组件中,避免污染全局样式;
​ components: 放置通用模块组件。项目里总会有一些复用的组件,例如弹出框、发送手机验证码、图片上传等,将它们作为通用组件,避免重复工作;
​ http: 放置与后台 api 相关的文件。这里面有 axios 库的实例配置文件、使用配置的 axios 实例接入 api 获取数据的函数的集合的文件;
​ mixins: 放置混合选项的文件。具体来说,相当于是公用函数的集合,在组件中引用时,可以作用于组件而不必书写重复的方法;
​ pages: 放置主要页面的组件。例如登录页、用户信息页等。通常是这里的组件本身写入一些结构,再引入通用模块组件,形成完整的页面;
​ router: 放置路由设置文件,指定路由对应的组件;
​ store: 放置 vuex 需要的状态关联文件,设置公共的 state、mutations 等;
​ App.vue: 入口组件,pages 里的组件会被插入此组件中,此组件再插入 index.html 文件里,形成单页面应用;
​ main.js: 入口 js 文件,影响全局,作用是引入全局使用的库、公共的样式和方法、设置路由等。

「滑动效果分析」

​ 滑动右侧列表时,更新当前分类, 点击某个分类项,右侧列表滑动到对应位置

​ 类名:取名 current 标识当前分类 ====> 设计一个计算属性:currentIndex

​ 根据哪些数据计算? ===> scrollY:右侧滑动的 Y轴坐标(滑动过程实时变化) tops: 所有右侧分类li的top组成的数组(列表第一次显示后就不再变化)

 export default {
    data() {
      return {
        scrollY: 0, // 右侧滑动的Y轴坐标 (滑动过程时实时变化)
        tops: [], // 所有右侧分类li的top组成的数组  (列表第一次显示后就不再变化)
        food: {}, // 需要显示的food
      }
    },
    
    //
 	 computed: {
      ...mapState(['goods']),

      // 计算得到当前分类的下标
      currentIndex() {// 初始和相关数据发生了变化
        // 得到条件数据
        const {scrollY, tops} = this
        // 根据条件计算产生一个结果
        const index = tops.findIndex((top, index) => {
          // scrollY>=当前top && scrollY<下一个top
          return scrollY >= top && scrollY < tops[index + 1]
        })
        // 返回结果
        return index
      }
    },
  
  
// ShopGoods.vue  

   this.$nextTick(() => { // 列表数据更新显示后执行
       this._initScroll()
       this._initTops()
   })
利用插件 betterscroll 优化滚动效果
import BScroll from '@better-scroll/core'
let scroll = new BScroll('.wrapper',{
    scrollY: true,
    click: true
})
// 这样就实现了一个具有纵向可点击的滚动效果的列表

// 具体配置文件
// 派发 scroll 的场景分为两种:
// 1. 手指作用在滚动区域(content DOM)上;
// 2. 调用 scrollTo 方法或者触发 momentum 滚动动画(其实底层还是调用 scrollTo 方法)

// 对于 v2.1.0 版本,对 probeType 做了一次统一
// 1. probeType 为 0,在任何时候都不派发 scroll 事件,
// 2. probeType 为 1,仅仅当手指按在滚动区域上,每隔 momentumLimitTime 毫秒派发一次 scroll 事件,
// 3. probeType 为 2,仅仅当手指按在滚动区域上,一直派发 scroll 事件,
// 4. probeType 为 3,任何时候都派发 scroll 事件,包括调用 scrollTo 或者触发 momentum 滚动动画

Vue.nextTick的使用
[Vue.nextTick( [callback, context\] )]
**参数**:
  - `{Function} [callback]`
  - `{Object} [context]`
**用法**:
  在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
  因为本身 DOM 更新是异步的,所以这里要调用 $nextTick 实行数据更改之后立即更新DOM

​ 为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用

​ 在组件内使用 vm.$nextTick() 实例方法特别方便,$nextTick() 返回一个 Promise 对象,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

在父组件中调用子组件对象的方法
 // 在父组件中 利用ref标识子组件标签
 <Food :food="food" ref="food"/>
// 显示food组件 (在父组件中调用子组件对象的方法),在父组件中就可以利用标签调用子组件对象
this.$refs.food.toggleShow()

补充:访问子组件实例或子元素

​ 尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:

<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput
「JavaScript的reduce函数用法」

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
reduce为数组中的每一个元素依次执行callback函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:
    accumulator 累计器
    currentValue 当前值
    currentIndex 当前索引
    array 数组
[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array){
  return accumulator + currentValue;
});

==
[0, 1, 2, 3, 4].reduce((prev, curr) => prev + curr );
// 硅谷外卖中的实例
  totalCount (state) {
    return state.cartFoods.reduce((preTotal, food) => preTotal + food.count , 0)
  },
// 初始值为0,返回 preTotal加上food的count属性值
「mapGetters用法」

​ 如果需要用到 state 里面的一个状态属性,则需要在子组件中引入mapGetters,将 store 中的 getter 映射到局部计算属性,而这说明在store文件里的getter.js中会有这两个方法。

...mapGetters(['totalCount', 'totalPrice']),
// store/getter.js
  totalCount (state) {
    return state.cartFoods.reduce((preTotal, food) => preTotal + food.count , 0)
  },

  totalPrice (state) {
    return state.cartFoods.reduce((preTotal, food) => preTotal + food.count*food.price , 0)
  },

补充,如果在子组件中写了mapState,在store文件里的state.js中要定义这个state

 // ShopCart.vue
 computed: {
      ...mapState(['cartFoods', 'info']),
      
 // state.js
  info: {}, // 商家信息
  cartFoods: [], // 购物车中食物的列表
「路由组件缓存保持组件状态」

​ 当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。

​ 重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive> 元素将其动态组件包裹

<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
「路由组件懒加载 」
// 利用 import函数使得 路由组件在需要的时候再加载
// import MSite from '../pages/MSite/MSite.vue'
// import Search from '../pages/Search/Search.vue'
// import Order from '../pages/Order/Order.vue'
// import Profile from '../pages/Profile/Profile.vue'

const MSite = () => import('../pages/MSite/MSite.vue')
const Search = () => import('../pages/Search/Search.vue')
const Order = () => import('../pages/Order/Order.vue')
const Profile = () => import('../pages/Profile/Profile.vue')
「图片懒加载 lazy-loading」
// main.js
import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload)

// or with options
const loadimage = require('./assets/loading.gif')
const errorimage = require('./assets/error.gif')

Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: errorimage,
  loading: loadimage,
  attempt: 1
})

new Vue({
  el: 'body',
  components: {
    App
  }
})

//template:
<ul>
  <li v-for="img in list">
    <img v-lazy="img.src" >
  </li>
</ul>

「Vue自定义过滤器」

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部

// fiters/index.js
import Vue from 'vue'
// import moment from 'moment'
import format from 'date-fns/format'

// 自定义过滤器
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
  // return moment(value).format(formatStr)
  return format(value, formatStr)
})

/* 具体使用方法
Vue.filter('module name', function (value) {
  return xxxx
})
*/


// main.js
import './fiters' // 加载过滤器

// 具体在组件中的使用
 <div class="time">{{rating.rateTime | date-format}}</div>  // | filter定义的名字
「Vue打包文件分析与优化」

​ 1) vue 脚手架提供了一个用于可视化分析打包文件的包webpack-bundle-analyzer 和配置
​ 2) 启用打包可视化: npm run build --report

image-20210607142703413

​ 可以发现 moment.js占用内存较大,所以可以考虑用date-fns来替换moment插件模块

// import moment from 'moment'
// import {format} from 'date-fns'
import format from 'date-fns/format'
import Vue from 'vue'
Vue.filter('dateString', function (value, formatStr) {
// return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss')
return format(value, formatStr || 'YYYY-MM-DD HH:mm:ss')
})
项目外卖App核心的商家模块的SPA,包括商品、评论、商家介绍、购物车等多个子模块,使用Vuejs全家桶+ES6+Webpack等前端最新最热的技术,采用模块化、组件化、工程化的模式开发; 显示/隐藏优惠和公告详情 <template> <div class="header"> <div class="content-wrapper"> <div class="avatar"> <img width="64" height="64"src="seller.avatar"> </div> <div class="content"> <div class="title"> <span class="brand"></span> <span class="name">{{seller.name}}</span> </div> <div class="description"> {{seller.description}}/{{seller.deliveryTime}}分钟到达 </div> <div class="support" v-if="seller.supports"> <span class="icon" :class="classMap[seller.supports[0].type]"></span> <span class="text">{{seller.supports[0].description}}</span> </div> </div> <div class="supports_count" v-if="seller.supports" @click="showDetail(true)"> <span class="count">{{seller.supports.length}}个</span> <span class="icon-keyboard_arrow_right"></span> </div> </div> <div class="bulletin-wrapper" @click="showDetail(true)"> <span class="bulletin-title"></span> <span class="bulletin-text">{{seller.bulletin}}</span> <i class="icon-keyboard_arrow_right"></i> </div> <div class="background"> <img width="100%" height="100%"src="seller.avatar"> </div> <div class="detail" v-show="detailShow"> <div class="detail-wrapper"> <div class="detail-main clearfix"> <div class="name">{{seller.name}}</div> <div class="star-wrapper">star组件内容</div> <div class="title"> <div class="line"></div> <div class="text">优惠信息</div> <div class="line"></div> </div> <ul class="supports" v-if="seller.supports"> <li class="support" v-for="item in seller.supports"> <span class="icon" :class="classMap[item.type]"></span> <span class="text">{{item.description}}</span> </li> </ul> <div class="title"> <div class="line"></div> <div class="text">商家公告</div> <div class="line"></div> </div> <div class="content"> <p>{{seller.bulletin}}</p> </div> </div> </div> <div class="detail-close" @click="showDetail(false)"> <span class="icon-close"></span> </div> </div> </div> </template> [removed] export default{ props: { seller: { type: Object } }, data () { return { detailShow: false } }, created () { this.classMap = ["decrease", "discount", "guarantee", "invoice", "special"] }, methods: { showDetail (isShow) { this.detailShow = isShow } } } [removed]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值