小程序项目学习--第四章:组件化-插槽-混入-生命周期-系统API-登录

第四章:组件化-插槽-混入-生命周期-系统API-登录

01_(理解)组件化-页面中直接调用子组件的方法

image-20230119143817967

可在父组件里调用 this.selectComponent ,获取子组件的实例对象 --1.获取对应的组件实例对象–通过定义的class获取

页面直接调用组件方法—必须给组件定义一个class类便于获取组件

子组件方法  
test(index) {
      console.log("tab control test function exec");
      this.setData({
        currentIndex: index
      })
    }

父组件
必须给组件定义一个class类便于获取组件
<!-- 4.tab-control的使用 -->
<tab-control
  class="tab-control"
  titles="{{digitalTitles}}"
  bind:indexchange="onTabIndexChange"
/>
<!-- 必须给组件定义一个class类便于获取组件 -->
<button bindtap="onExecTCMethod">调用TC方法</button>

父组件在方法中调用子组件
  onExecTCMethod() {
    // 1.获取对应的组件实例对象--通过定义的class获取
    const tabControl = this.selectComponent(".tab-control")
    // 2.调用组件实例的方法
    tabControl.test(2)
  }

02_(掌握)组件化-插槽-单个插槽的使用和默认值

image-20230119193449005

image-20230119193458303

要点:小程序中slot插槽不支持默认值

通过css实现插值默认值,通过兄弟选择器

<view class="content">
    <!-- 小程序中插槽是不支持默认值的 -->
    <slot></slot>
  </view>
  <view class="default">哈哈哈哈</view>


.default {
  display: none;
}

.content:empty + .default {
  display: block;
}

代码实现

my-slot

<!--components/my-slot/my-slot.wxml-->
<view class="my-slot">
  <view class="header">Header</view>
  <view class="content">
    <!-- 小程序中插槽是不支持默认值的 -->
    <slot></slot>
  </view>
  <view class="default">默认值</view>
  <view class="footer">Footer</view>
</view>

/* components/my-slot/my-slot.wxss */
.my-slot {
  margin: 20px 0;  
}

.default {
  display: none;
}

.content:empty + .default {
  display: block;
}

08_learn_slot

{
  "usingComponents": {
    "my-slot": "/components/my-slot/my-slot"
  }
}

<!--pages/08_learn_slot/index.wxml-->
<!-- 1.单个插槽的使用 -->
<my-slot>
  <button>我是按钮</button>
</my-slot>
<my-slot>
  <image src="/assets/nhlt.jpg" mode="widthFix"></image>
</my-slot>

<my-slot></my-slot>

03_(掌握)组件化-插槽-多个插槽的使用和配置

image-20230119195634091

-多个插槽的使用需要给插槽命名(具名插槽)和在Component实例中设置options:{multipleSlots:true}

1.使用多个插槽需要在Component实例中设置options:{multipleSlots:true}

2.给插槽命名具名插槽

3.给组件设置样式

4.在页面中json中注册组件usingComponents

5.使用组件–给标签添加slot属性并指定查找名

1.// components/mul-slot/mul-slot.js
Component({
  options: {
    multipleSlots: true
  }
})
2.
<!--components/mul-slot/mul-slot.wxml-->
<view class="mul-slot">
  <view class="left">
  <!-- 具名插槽 -->
    <slot name="left"></slot>
  </view>
  <view class="center">
    <slot name="center"></slot>
  </view>
  <view class="right">
    <slot name="right"></slot>
  </view>
</view>
3.
/* components/mul-slot/mul-slot.wxss */
.mul-slot {
  display: flex;
  text-align: center;
}

.left, .right {
  width: 160rpx;
}

.center {
  flex: 1;
}
4.
{
  "usingComponents": {
    "mul-slot": "/components/mul-slot/mul-slot"

  }
}
5.
<!-- 2.多个插槽的使用 -->
<mul-slot>
<!-- 给标签添加slot属性并指定查找名 -->
  <button slot="left" size="mini">left</button>
  <view slot="center">哈哈哈</view>
  <button slot="right" size="mini">right</button>
</mul-slot>

04_(理解)组件化-组件的代码复用-Behaviors

image-20230119201529330

behaviors 是用于组件间代码共享的特性,类似于一些编程语言中的 “mixins”。

混入的实现步骤

1.注册组件、使用组件

2.定义混入的js文件

3.在组件中·使用混入,导入、注册、使用

 1. 
"c-behavior": "/components/c-behavior/c-behavior"

<!-- 3.组件的混入 -->
<c-behavior/>
    
2.
export const counterBehavior = Behavior({
  data: {
    counter: 100
  },
  methods: {
    increment() {
      this.setData({ counter: this.data.counter + 1 })
    },
    decrement() {
      this.setData({ counter: this.data.counter - 1 })
    }
  }
})

3.
// components/c-behavior/c-behavior.js
// 使用类似mixins的behaviors的混入的步骤
// 1.导入
import { counterBehavior } from "../../behaviors/counter"
// 2.注册
Component({
  behaviors: [counterBehavior]
})

<!--components/c-behavior/c-behavior.wxml-->
<view>
<!-- 3.使用混入的数据 -->
  <view class="counter">当前计数: {{counter}}</view>
  <button bindtap="increment">+1</button>
  <button bindtap="decrement">-1</button>
</view>

05_(理解)组件化-组件的生命周期函数

image-20230119203809951

image-20230119203820671

  1. 组件生命周期的针对于本组件,页面的生命周期针对于本页面
  2. 注册、使用
1.
<text>c-lifetime组件</text>

// components/c-lifetime/c-lifetime.js
Component({
  // 组件生命周期的书写位置
  lifetimes: {
    created() {
      console.log("组件被创建created");
    },
    attached() {
      console.log("组件被添加到组件树中attached");
    },
    detached() {
      console.log("组件从组件树中被移除detached");
    }
  },
  // 页面的生命周期
  pageLifetimes: {
    show() {
      console.log("page show");
    },
    hide() {
      console.log("page hide");
    }
  }
})
2.
    "c-lifetime": "/components/c-lifetime/c-lifetime"

<!-- 4.组件的生命周期 -->
<button bindtap="onChangeTap">切换</button>
<c-lifetime wx:if="{{isShowLiftTime}}"/>

// pages/08_learn_slot/index.js
Page({
  data: {
    isShowLiftTime: true
  },
  onChangeTap() {
    this.setData({ isShowLiftTime: !this.data.isShowLiftTime })
  }
})

06_(掌握)组件化-Component函数可以传入的选项

image-20230119204619753

image-20230119204627351

第四章:小程序系统API调用

image-20230119204718260

07_(了解)API-常见API的内容介绍

image-20230119204955490

08_(掌握)API-网络请求API的基本使用

image-20230119212810365

image-20230119212820465

1.在onload中发起网络请求wx,request({})传入参数

2.在wxml中展示数据

1.
// pages/09_learn_api/index.js

Page({
  data: {
    allCities: {},
    houselist: [],
  },
  onLoad() {
    // 1.网络请求基本使用
    // wx.request({
    //   url: "http://123.207.32.32:1888/api/city/all",
    //   success: (res) => {
    //     const data = res.data.data
    //     console.log(data);
    //     this.setData({ allCities: data })
    //   },
    //   fail: (err) => {
    //     console.log("err:", err);
    //   }
    // })
    wx.request({
      url: "http://123.207.32.32:1888/api/home/houselist",
      data:{page:1},
      success: (res) => {
        const data = res.data.data
        console.log(data);
        this.setData({ houselist: data })
      }
    })
  }
})
2.
<!--pages/09_learn_api/index.wxml-->
<view class="house-list">
  <block wx:for="{{houselist}}" wx:key="{{item.data.houseId}}">
    <view class="item">
      <image src="{{item.data.image.url}}"></image>
      <view class="title">{{item.data.houseName}}</view>
    </view>
  </block>
</view>

09_(理解)API-网络请求域名的后台配置

image-20230119213501856

10_(掌握)API-网络请求的函数封装和使用

网络请求封装成函数实现步骤

1.在service文件夹中创建index.js文件,通过解构和promise封装

1-2.引入service文件夹中的index.js文件

2.使用封装的函数 promise有点并行的感觉,第一个请求的结果没出来之前也会发送第二个请求

3.await/async 同步 第一个请求的结果没出来之前不会发送第二个请求,容易堵塞,不过可以放到单独的方法中

4.将请求封装到一个单独函数中(推荐),异步方法不会阻塞。方法单独定义,在onLoad调用就行

5.下拉加载更多

1.
// 封装成函数
export function hyRequest(options) {
  return new Promise((resolve, reject) => {
    wx.request({ 
      ...options, 
      success: (res) => {
        resolve(res.data)
      },
      fail: reject
    })
  })
}
1-2
// pages/09_learn_api/index.js
import { hyRequest, hyReqInstance } from "../../service/index"

Page({
  data: {
    allCities: {},
    houselist: [],
    currentPage: 1
  },
  async onLoad() {
    // 1.网络请求基本使用
    // wx.request({
    //   url: "http://codercba.com:1888/api/city/all",
    //   success: (res) => {
    //     const data = res.data.data
    //     this.setData({ allCities: data })
    //   },
    //   fail: (err) => {
    //     console.log("err:", err);
    //   }
    // })

    // wx.request({
    //   url: 'http://codercba.com:1888/api/home/houselist',
    //   data: {
    //     page: 1
    //   },
    //   success: (res) => {
    //     const data = res.data.data
    //     this.setData({ houselist: data })
    //   }
    // })
2.
    // 2.使用封装的函数 promise有点并行的感觉,第一个请求的结果没出来之前也会发送第二个请求,
    // hyRequest({ 
    //   url: "http://codercba.com:1888/api/city/all" 
    // }).then(res => {
    //   this.setData({ allCities: res.data })
    // })

    // hyRequest({
    //   url: "http://codercba.com:1888/api/home/houselist",
    //   data: {
    //     page: 1
    //   }
    // }).then(res => {
    //   this.setData({ houselist: res.data })
    // })
3.
    // 3.await/async 同步 第一个请求的结果没出来之前不会发送第二个请求,容易堵塞,不过可以放到单独的方法中
    // const cityRes = await hyRequest({ 
    //   url: "http://codercba.com:1888/api/city/all" 
    // })
    // this.setData({ allCities: cityRes.data })

    // const houseRes = await hyRequest({
    //   url: "http://codercba.com:1888/api/home/houselist",
    //   data: { page: 1 }
    // })
    // this.setData({ houselist: houseRes.data })
4.
    // 4.将请求封装到一个单独函数中(推荐),异步方法不会阻塞
    this.getCityData()
    this.getHouselistData()

    // 5.使用类的实例发送请求
    hyReqInstance.get({
      url: "/city/all"
    }).then(res => {
      console.log(res);
    })
  },
5.
  async getCityData() {
    const cityRes = await hyRequest({ 
      url: "http://codercba.com:1888/api/city/all" 
    })
    this.setData({ allCities: cityRes.data })
  },
  async getHouselistData() {
    const houseRes = await hyRequest({
      url: "http://codercba.com:1888/api/home/houselist",
      data: { page: this.data.currentPage }
    })
    const finalHouseList = [...this.data.houselist, ...houseRes.data]
    this.setData({ houselist: finalHouseList })
    // 思考: 为什么这里不需要setData?因为页面不需要刷新
    this.data.currentPage++
  },
5.  // 下拉加载更多
  onReachBottom() {
    this.getHouselistData()
  }
})

可不可以怎么写

   一个测试
hyRequest({ 
      url: "http://codercba.com:1888/api/city/all" 
    }).then(res => {
      this.setData({ allCities: res.data })
    },err=>{console.log("err");})

11_(掌握)API-网络请求的类封装和使用过程

image-20230119223725723

1.先封装class的网络请求–封装成类

2.引入class

3.使用类的实例发送request请求

  1.
// 封装成类 -> 实例
class HYRequest {
  constructor(baseURL) {
    this.baseURL = baseURL
  }
  request(options) {
    const { url } = options
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,
        url: this.baseURL + url,
        success: (res) => {
          resolve(res.data)
        },
        fail: (err) => {
          console.log("err:", err);
        }
      })
    })
  }
  get(options) {
    return this.request({ ...options, method: "get" })
  }
  post(options) {
    return this.request({ ...options, method: "post" })
  }
}

export const hyReqInstance = new HYRequest("http://codercba.com:1888/api")

2.
import { hyRequest, hyReqInstance } from "../../service/index"

3.
  // 5.使用类的实例发送请求
    hyReqInstance.get({
      url: "/city/all"
    }).then(res => {
      console.log(res);
    })
    // 6.使用类的实例发送request请求
    // hyReqInstance.request({
    //   url: "/home/houselist?page=1"
    // }).then(res => {
    //   this.setData({ houselist: res.data })
    // })

12_(了解)API-小程序中的弹窗相关API

image-20230120100033263

<!--pages/10_learn_api/index.wxml-->
<!-- 1.展示弹窗 -->
<view>
  <button size="mini" bindtap="onShowToast">showToast</button>
  <button size="mini" bindtap="onShowModal">showModal</button>
  <button size="mini" bindtap="onShowAction">showAction</button>
</view>


// pages/10_learn_api/index.js
Page({
  // 1.弹窗相关的API
  onShowToast() {
    wx.showToast({
      title: '购买失败!',
      icon: "error",
      duration: 1000,
        //蒙版:在未消失的时候可不可以点击后面的操作
      mask: true,
      success: (res) => {
        console.log("res:", res);
      },
      fail: (err) => {
        console.log("err:", err);
      }
    })

    // wx.showLoading({
    //   title: "加载中ing"
    // })
  },
  onShowModal() {
    wx.showModal({
      title: "确定购买吗?",
      content: "确定购买的话, 请确定您的微信有钱!",
      confirmColor: "#f00",
      cancelColor: "#0f0",
      success: (res) => {
        if (res.cancel) {
          console.log("用户点击取消");
        } else if (res.confirm) {
          console.log("用户点击了确定");
        }
      }
    })
  },
  onShowAction() {
    wx.showActionSheet({
      itemList: ["衣服", "裤子", "鞋子"],
      success: (res) => {
        console.log(res.tapIndex);
      },
      fail: (err) => {
        console.log("err:", err);
      }
    })
  },

 
  
}  	

13_(理解)API-小程序的分享API回调函数

image-20230120101122298

// 2.分享功能
  onShareAppMessage() {
    return {
      title: "旅途的内容",
      path: "/pages/favor/favor",
      imageUrl: "/assets/nhlt.jpg"
    }
  },

14_(掌握)API-小程序的设备信息和位置信息

image-20230120101324354

image-20230120102556422

对于用户的关键信息,需要获取用户的授权后才能获得:

app.json中配置
"permission": {
    "scope.userLocation": {
      "desc": "需要获取您的位置信息"
    }
  },
      

<!-- 3.设备信息 -->
<button bindtap="onGetSystemInfo">设备信息</button>
      
  // 3.获取用户的设备信息
  onGetSystemInfo() {
    // 1.获取手机的基本信息
    // wx.getSystemInfo({
    //   success: (res) => {
    //     console.log(res);
    //   }
    // })

    // 2.获取当前的位置信息
    wx.getLocation({
      success: (res) => {
        console.log("res:", res);
      }
    })
  },      

15_(掌握)API-本地存储Storage的API使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y12gk531-1675304800111)(null)]

<!-- 4.本地存储 -->
<button bindtap="onLocalStorage">本地存储</button>

// 4.本地存储方式
  onLocalStorage() {
    // 同步操作,执行完上一行,才可以执行下一行
    // 1.存储一些键值对
    // wx.setStorageSync('name', "why")
    // wx.setStorageSync('age', 18)
    // wx.setStorageSync('friends', ["abc", "cba", "nba"])

    // 2.获取storage中内容
    // const name = wx.getStorageSync('name')
    // const age = wx.getStorageSync('age')
    // const friends = wx.getStorageSync('friends')
    // console.log(name, age, friends);

    // 3.删除storage中内容
    // wx.removeStorageSync('name')

    // 4.清空storage中内容
    // wx.clearStorageSync()

    // 异步操作 ,执行的时候不会阻塞下一行代码的执行
    wx.setStorage({
      key: "books",
      data: "哈哈哈",
      // 加密但是有版本限制--2.21.3
      encrypt: true,
      success: (res) => {
        wx.getStorage({
          // 获取
          key: "books",
          encrypt: true,
          success: (res) => {
            console.log(res);
          }
        })
      }
    })

    console.log("-------");
  }

16_(了解)API-页面跳转-跳转不同的方法

页面跳转存在页面栈–会记录页面跳转的记录,以下方法记录跳转的不同方法

image-20230120110656051

17_(掌握)API-页面的跳转和页面的返回

image-20230120112039188

页面跳转wx.navigateTo({})

<button bindtap="onNavTap">跳转</button>

// pages/11_learn_nav/index.js
Page({
  data: {
    name: "kobe",
    age: 30,
  },
  onNavTap() {
    const name = this.data.name
    const age = this.data.age

    // 页面导航操作
    wx.navigateTo({
      // 跳转的过程, 传递一些参数过去
      url: `/pages2/detail/detail?name=${name}&age=${age}`,
    })
  }
})

跳转过后的页面
// pages2/detail/detail.js
Page({
  data: {
    name: "",
    age: 0
  },

  onLoad(options) {
    // options里面就是跳转时携带的参数
    console.log(options);
    const name = options.name
    const age = options.age
    this.setData({ name, age })


  },
})

image-20230120112558104

<button size="mini" type="primary" bindtap="onBackTap">返回</button>

 onBackTap() {
    // 1.返回导航
    wx.navigateBack({
      // 默认值是1.跳转上级页面目录
      delta:2
    })
  }

18_(掌握)API-页面的返回数据传递方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-voZBEjeV-1675304800287)(null)]

image-20230120114240862

<view>{{message}}</view>

message: "哈哈哈"

detail
  <button size="mini" type="primary" bindtap="onBackTap">返回</button>    

onBackTap() {
    // 1.返回导航
    wx.navigateBack()

    // 2.方式一: 给上一级的页面传递数据
    // 2.1. 获取到上一个页面的实例
    // const pages = getCurrentPages()
    // // console.log(pages);通过打印看当前页面和上一个页面的位置,通过pages[pages.length-2]获取到上一个页面
    // const prePage = pages[pages.length-2]
    // // 2.2.通过setData给上一个页面设置数据
    // prePage.setData({ message: "呵呵呵" })
  },
    //  页面卸载的生命周期,只用在这里写,就都可以实现了
    onUnload() {
      // 2.1. 获取到上一个页面的实例
      const pages = getCurrentPages()
      const prePage = pages[pages.length-2]
  
      // 2.2.通过setData给上一个页面设置数据
      prePage.setData({ message: "呵呵呵" })
    }

19_(掌握)API-页面的返回数据传递方式二

image-20230120115739876

// pages/11_learn_nav/index.js
Page({
  data: {
    name: "kobe",
    age: 30,
    message: "哈哈哈"
  },
  onNavTap() {
    const name = this.data.name
    const age = this.data.age
 3.   const that = this
    // 页面导航操作
    wx.navigateTo({
      // 跳转的过程, 传递一些参数过去
      url: `/pages2/detail/detail?name=${name}&age=${age}`,
      // 方式二,通过 events:回调函数接受数据
2.      events: {
        backEvent(data) {
          console.log("back:", data);
          const names = data.name
3.          // 修改值携带进来
          that.setData({name:names})
        },
        coderwhy(data) {
          console.log("why:", data);
        }
      }
    })
  }
})

 onBackTap() {
    // 1.返回导航
    wx.navigateBack()

    // 2.方式一: 给上一级的页面传递数据
    // 2.1. 获取到上一个页面的实例
    // const pages = getCurrentPages()
    // // console.log(pages);通过打印看当前页面和上一个页面的位置,通过pages[pages.length-2]获取到上一个页面
    // const prePage = pages[pages.length-2]

    // // 2.2.通过setData给上一个页面设置数据
    // prePage.setData({ message: "呵呵呵" })

2.    // 3.方式二: 回调events的函数
    // 3.1. 拿到eventChannel
    const eventChannel = this.getOpenerEventChannel()

    // 3.2. 通过eventChannel回调函数
    eventChannel.emit("backEvent", { name: "back", age: 111 })
    eventChannel.emit("coderwhy", { name: "why", age: 10 })


  },

20_(了解)API-navigator组件的跳转和返回

image-20230120121732362

<navigator open-type="navigateBack">返回</navigator>

<navigator class="nav" url="/pages2/detail/detail?name=why&age=18">跳转</navigator>

21_(理解)登录流程-登录中身份标识的验证

image-20230120121917021

image-20230120121930698

image-20230120122400861

小程序登录不用登录账号和密码,因为直接在微信上登录

image-20230120123956851

image-20230120124758824

小程序通过openid来验证身份

如果账号注册公众号可以获取unionid

22_(掌握)登录流程-登录流程的解析和代码演练

image-20230120125451671

image-20230120130847916

实现步骤登录逻辑

1.方式一:通过原生方法

import { hyLoginReqInstance } from "../../service/index"
// pages/12_learn_login/index.js
Page({
  // onLoad登录的流程
  async onLoad() {
    // 1.获取token, 判断token是否有值
    const token = wx.getStorageSync('token') || ""

    // 2.发起请求--判断token是否过期
    const res = await hyLoginReqInstance.post({
      url: "/auth",
      header: {
        token: token
      }
    })
    // console.log(res);

    // 2.如果token有值和无值的情况
    if (token && res.message === "已登录") {
      console.log("请求其他的数据");
    } else {
      this.handleLogin()
    }
  },


  // 第一种写法
  // handleLogin() {
  //   // 1.获取code
  //   wx.login({
  //     success: (res) => {
  //       const code = res.code

  //       // 2.将这个code发送自己的服务器(后端)
  //       wx.request({
  //         url: "http://123.207.32.32:3000/login",
  //         data: {
  //           code
  //         },
  //         method: "post",
  //         success: (res) => {
  //           const token = res.data.token
  //           wx.setStorageSync('token', token)
  //         }
  //       })
  //     }
  //   })
  // }
})

2.方式二:通过promise–

1.封装login的promise方法

2.封装成类的请求方法

3.onLoad登录的流程

3.1.获取token, 判断token是否有值

3.2.判断token是否过期

3.3如果token有值和无值的情况

4.第二种写法 --获取login接口处理函数获取token

login.js
export function getCode(){
  return new Promise((resolve,reject)=>{
    wx.login({
        success:(res)=>{
          resolve(res.code)
        }
    })
  })
}
export const hyLoginReqInstance = new HYRequest("http://123.207.32.32:3000")

import { getCode } from "../../service/login";
import { hyLoginReqInstance } from "../../service/index"

// pages/12_learn_login/index.js
Page({
  // onLoad登录的流程
  async onLoad() {
    // 1.获取token, 判断token是否有值
    const token = wx.getStorageSync('token') || ""

    // 2.发起请求--判断token是否过期--判断token是否正确
    const res = await hyLoginReqInstance.post({
      url: "/auth",
      header: {
        token: token
      }
    })
    // console.log(res);

    // 2.如果token有值和无值的情况
    if (token && res.message === "已登录") {
      console.log("请求其他的数据");
    } else {
      this.handleLogin()
    }
  },
 // 第二种写法 --获取login接口处理函数获取token
  async handleLogin() {
    // 1.获取code
    const code = await getCode()

    // 2.使用code换取token
    const res = await hyLoginReqInstance.post({
      url: "/login",
      data: { code }
    })

    // 3.保存token
    wx.setStorageSync('token', res.token)
  }

})

23_(理解)内容回顾和作业布置

第四章:内容回顾

一. 组件化开发

1.1. 直接调用子组件的方法

1.2. 插槽的使用

  • 单个插槽
    • 默认值
  • 多个插槽

1.3. Behaviors混入

1.4. 生命周期函数

  • 组件的生命周期函数
  • 页面的生命周期函数

1.5. Component选项

二. 系统API调用

2.1. 网络请求API

  • 基本使用
  • 域名配置
  • 工具封装:
    • 函数封装
    • 类封装

2.2. 弹窗和分享

  • showToast
  • showModal
  • showActionSheet
  • onShareAppMessage

2.3. 设备和位置信息

  • wx.getSystemInfo
  • wx.getLocation()
    • app.json配置

2.4. Storage的存储

2.5. 页面跳转和数据传递

  • navigateTo
  • navigateBack

数据传递:

  • ?queryString
  • getCurrentPages -> prePage.setData
  • events -> eventChannel

组件跳转

  • navigator组件

2.6. 登录流程

  • 登录的身份的标题

    • openid
  • 登录流程

  • 登录代码演练

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值